From f9f6e207d463cc1750f5249e46690f08b511c3d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20G=C3=B6ls?= <6608231+Abrynos@users.noreply.github.com> Date: Mon, 25 Mar 2024 22:58:03 +0100 Subject: [PATCH] Add official monitoring plugin (#3160) * Add Monitoring plugin * Prepare pipeline * Fix Rider stupidity * Fix Windows build * Remove translation files * Apply feedback * Add steam id as additional tag to metrics * Apply feedback * Add runtime metrics * Fix my brain not braining * Use extension methods to add instrumentation and Add monitoring for outbound HTTP traffic * Upgrade OpenTelemetry.Extensions.Hosting to prerelease due to runtime exception * Remove config and add file that was supposed to be committed yesterday to fix the runtime exception * Revert changes to publish.yml * Remove localization * Apply feedback * Apply feedback * Fix version number * Revert use of property in Kestrel (even tho it's an outside caller to the source class) --- ...teamFarm.OfficialPlugins.Monitoring.csproj | 21 ++ .../AssemblyInfo.cs | 26 +++ .../MonitoringPlugin.cs | 190 ++++++++++++++++++ .../TagNames.cs | 31 +++ ArchiSteamFarm.sln | 8 + ArchiSteamFarm/AssemblyInfo.cs | 2 + ArchiSteamFarm/IPC/ArchiKestrel.cs | 18 ++ .../Plugins/Interfaces/IWebServiceProvider.cs | 46 +++++ ArchiSteamFarm/Plugins/PluginsCore.cs | 3 + Directory.Packages.props | 5 + 10 files changed, 350 insertions(+) create mode 100644 ArchiSteamFarm.OfficialPlugins.Monitoring/ArchiSteamFarm.OfficialPlugins.Monitoring.csproj create mode 100644 ArchiSteamFarm.OfficialPlugins.Monitoring/AssemblyInfo.cs create mode 100644 ArchiSteamFarm.OfficialPlugins.Monitoring/MonitoringPlugin.cs create mode 100644 ArchiSteamFarm.OfficialPlugins.Monitoring/TagNames.cs create mode 100644 ArchiSteamFarm/Plugins/Interfaces/IWebServiceProvider.cs diff --git a/ArchiSteamFarm.OfficialPlugins.Monitoring/ArchiSteamFarm.OfficialPlugins.Monitoring.csproj b/ArchiSteamFarm.OfficialPlugins.Monitoring/ArchiSteamFarm.OfficialPlugins.Monitoring.csproj new file mode 100644 index 000000000..f4f3195be --- /dev/null +++ b/ArchiSteamFarm.OfficialPlugins.Monitoring/ArchiSteamFarm.OfficialPlugins.Monitoring.csproj @@ -0,0 +1,21 @@ + + + Library + + + + + + + + + + + + + + + + + + diff --git a/ArchiSteamFarm.OfficialPlugins.Monitoring/AssemblyInfo.cs b/ArchiSteamFarm.OfficialPlugins.Monitoring/AssemblyInfo.cs new file mode 100644 index 000000000..370744c5e --- /dev/null +++ b/ArchiSteamFarm.OfficialPlugins.Monitoring/AssemblyInfo.cs @@ -0,0 +1,26 @@ +// ---------------------------------------------------------------------------------------------- +// _ _ _ ____ _ _____ +// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___ +// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \ +// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | +// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| +// ---------------------------------------------------------------------------------------------- +// | +// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki +// Contact: JustArchi@JustArchi.net +// | +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// | +// http://www.apache.org/licenses/LICENSE-2.0 +// | +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; + +[assembly: CLSCompliant(true)] diff --git a/ArchiSteamFarm.OfficialPlugins.Monitoring/MonitoringPlugin.cs b/ArchiSteamFarm.OfficialPlugins.Monitoring/MonitoringPlugin.cs new file mode 100644 index 000000000..e1ff7e97d --- /dev/null +++ b/ArchiSteamFarm.OfficialPlugins.Monitoring/MonitoringPlugin.cs @@ -0,0 +1,190 @@ +// ---------------------------------------------------------------------------------------------- +// _ _ _ ____ _ _____ +// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___ +// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \ +// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | +// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| +// ---------------------------------------------------------------------------------------------- +// | +// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki +// Contact: JustArchi@JustArchi.net +// | +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// | +// http://www.apache.org/licenses/LICENSE-2.0 +// | +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Composition; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Metrics; +using System.Linq; +using System.Text.Json.Serialization; +using System.Threading.Tasks; +using ArchiSteamFarm.Core; +using ArchiSteamFarm.IPC.Integration; +using ArchiSteamFarm.Plugins; +using ArchiSteamFarm.Plugins.Interfaces; +using ArchiSteamFarm.Steam; +using ArchiSteamFarm.Storage; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using OpenTelemetry.Metrics; +using SteamKit2; + +namespace ArchiSteamFarm.OfficialPlugins.Monitoring; + +[Export(typeof(IPlugin))] +[SuppressMessage("ReSharper", "MemberCanBeFileLocal")] +internal sealed class MonitoringPlugin : OfficialPlugin, IWebServiceProvider, IGitHubPluginUpdates, IDisposable { + private const string MeterName = SharedInfo.AssemblyName; + + private const string MetricNamePrefix = "asf"; + + private static bool Enabled => ASF.GlobalConfig?.IPC ?? GlobalConfig.DefaultIPC; + + [JsonInclude] + [Required] + public override string Name => nameof(MonitoringPlugin); + + /// + public string RepositoryName => SharedInfo.GithubRepo; + + [JsonInclude] + [Required] + public override Version Version => typeof(MonitoringPlugin).Assembly.GetName().Version ?? throw new InvalidOperationException(nameof(Version)); + + private Meter? Meter; + + public void Dispose() => Meter?.Dispose(); + + public void OnConfiguringEndpoints(IApplicationBuilder app) { + ArgumentNullException.ThrowIfNull(app); + + if (!Enabled) { + return; + } + + app.UseEndpoints(static builder => builder.MapPrometheusScrapingEndpoint()); + } + + public void OnConfiguringServices(IServiceCollection services) { + ArgumentNullException.ThrowIfNull(services); + + if (!Enabled) { + return; + } + + InitializeMeter(); + + services.AddOpenTelemetry().WithMetrics( + builder => { + builder.AddPrometheusExporter(static config => config.ScrapeEndpointPath = "/Api/metrics"); + builder.AddRuntimeInstrumentation(); + builder.AddAspNetCoreInstrumentation(); + builder.AddHttpClientInstrumentation(); + builder.AddMeter(Meter.Name); + } + ); + } + + public override Task OnLoaded() => Task.CompletedTask; + + [MemberNotNull(nameof(Meter))] + private void InitializeMeter() { + if (Meter != null) { + return; + } + + Meter = new Meter(MeterName, Version.ToString()); + + Meter.CreateObservableGauge( + $"{MetricNamePrefix}_ipc_banned_ips", + static () => ApiAuthenticationMiddleware.GetCurrentlyBannedIPs().Count(), + description: "Number of IP addresses currently banned by ASFs IPC module" + ); + + Meter.CreateObservableGauge( + $"{MetricNamePrefix}_active_plugins", + static () => PluginsCore.ActivePluginsCount, + description: "Number of plugins currently loaded in ASF" + ); + + Meter.CreateObservableGauge( + $"{MetricNamePrefix}_bots", static () => { + ICollection bots = Bot.Bots?.Values ?? Array.Empty(); + + return new List>(4) { + new(bots.Count, new KeyValuePair(TagNames.BotState, "configured")), + new(bots.Count(static bot => bot.IsConnectedAndLoggedOn), new KeyValuePair(TagNames.BotState, "online")), + new(bots.Count(static bot => !bot.IsConnectedAndLoggedOn), new KeyValuePair(TagNames.BotState, "offline")), + new(bots.Count(static bot => bot.CardsFarmer.NowFarming), new KeyValuePair(TagNames.BotState, "farming")) + }; + }, + description: "Number of bots that are currently loaded in ASF" + ); + + Meter.CreateObservableGauge( + $"{MetricNamePrefix}_bot_friends", static () => { + ICollection bots = Bot.Bots?.Values ?? Array.Empty(); + + return bots.Where(static bot => bot.IsConnectedAndLoggedOn).Select(static bot => new Measurement(bot.SteamFriends.GetFriendCount(), new KeyValuePair(TagNames.BotName, bot.BotName), new KeyValuePair(TagNames.SteamID, bot.SteamID))); + }, + description: "Number of friends each bot has on Steam" + ); + + Meter.CreateObservableGauge( + $"{MetricNamePrefix}_bot_clans", static () => { + ICollection bots = Bot.Bots?.Values ?? Array.Empty(); + + return bots.Where(static bot => bot.IsConnectedAndLoggedOn).Select(static bot => new Measurement(bot.SteamFriends.GetClanCount(), new KeyValuePair(TagNames.BotName, bot.BotName), new KeyValuePair(TagNames.SteamID, bot.SteamID))); + }, + description: "Number of Steam groups each bot is in" + ); + + Meter.CreateObservableGauge( + $"{MetricNamePrefix}_bot_farming_minutes_remaining", static () => { + ICollection bots = Bot.Bots?.Values ?? Array.Empty(); + + return bots.Select(static bot => new Measurement(bot.CardsFarmer.TimeRemaining.TotalMinutes, new KeyValuePair(TagNames.BotName, bot.BotName), new KeyValuePair(TagNames.SteamID, bot.SteamID))); + }, + description: "Approximate number of minutes remaining until each bot has finished farming all cards" + ); + + Meter.CreateObservableGauge( + $"{MetricNamePrefix}_bot_heartbeat_failures", static () => { + ICollection bots = Bot.Bots?.Values ?? Array.Empty(); + + return bots.Select(static bot => new Measurement(bot.HeartBeatFailures, new KeyValuePair(TagNames.BotName, bot.BotName), new KeyValuePair(TagNames.SteamID, bot.SteamID))); + }, + description: "Number of times a bot has failed to reach Steam servers" + ); + + Meter.CreateObservableGauge( + $"{MetricNamePrefix}_bot_wallet_balance", static () => { + ICollection bots = Bot.Bots?.Values ?? Array.Empty(); + + return bots.Where(static bot => bot.WalletCurrency != ECurrencyCode.Invalid).Select(static bot => new Measurement(bot.WalletBalance, new KeyValuePair(TagNames.BotName, bot.BotName), new KeyValuePair(TagNames.SteamID, bot.SteamID), new KeyValuePair(TagNames.CurrencyCode, bot.WalletCurrency.ToString()))); + }, + description: "Current Steam wallet balance of each bot" + ); + + Meter.CreateObservableGauge( + $"{MetricNamePrefix}_bot_bgr_keys_remaining", static () => { + ICollection bots = Bot.Bots?.Values ?? Array.Empty(); + + return bots.Select(static bot => new Measurement(bot.GamesToRedeemInBackgroundCount, new KeyValuePair(TagNames.BotName, bot.BotName), new KeyValuePair(TagNames.SteamID, bot.SteamID))); + }, + description: "Remaining games to redeem in background per bot" + ); + } +} diff --git a/ArchiSteamFarm.OfficialPlugins.Monitoring/TagNames.cs b/ArchiSteamFarm.OfficialPlugins.Monitoring/TagNames.cs new file mode 100644 index 000000000..bbe3ea154 --- /dev/null +++ b/ArchiSteamFarm.OfficialPlugins.Monitoring/TagNames.cs @@ -0,0 +1,31 @@ +// ---------------------------------------------------------------------------------------------- +// _ _ _ ____ _ _____ +// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___ +// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \ +// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | +// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| +// ---------------------------------------------------------------------------------------------- +// | +// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki +// Contact: JustArchi@JustArchi.net +// | +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// | +// http://www.apache.org/licenses/LICENSE-2.0 +// | +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace ArchiSteamFarm.OfficialPlugins.Monitoring; + +internal static class TagNames { + internal const string BotName = "bot"; + internal const string BotState = "state"; + internal const string CurrencyCode = "currency"; + internal const string SteamID = "steamid"; +} diff --git a/ArchiSteamFarm.sln b/ArchiSteamFarm.sln index 441a15454..a23907a51 100644 --- a/ArchiSteamFarm.sln +++ b/ArchiSteamFarm.sln @@ -19,6 +19,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArchiSteamFarm.CustomPlugin EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArchiSteamFarm.OfficialPlugins.MobileAuthenticator", "ArchiSteamFarm.OfficialPlugins.MobileAuthenticator\ArchiSteamFarm.OfficialPlugins.MobileAuthenticator.csproj", "{8D85BCCA-4DE6-4CC0-B015-E2E89E8E8AA3}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArchiSteamFarm.OfficialPlugins.Monitoring", "ArchiSteamFarm.OfficialPlugins.Monitoring\ArchiSteamFarm.OfficialPlugins.Monitoring.csproj", "{0213FBF7-C06E-4E76-9F10-D58D5CB99339}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -77,5 +79,11 @@ Global {8D85BCCA-4DE6-4CC0-B015-E2E89E8E8AA3}.DebugFast|Any CPU.Build.0 = Debug|Any CPU {8D85BCCA-4DE6-4CC0-B015-E2E89E8E8AA3}.Release|Any CPU.ActiveCfg = Release|Any CPU {8D85BCCA-4DE6-4CC0-B015-E2E89E8E8AA3}.Release|Any CPU.Build.0 = Release|Any CPU + {0213FBF7-C06E-4E76-9F10-D58D5CB99339}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0213FBF7-C06E-4E76-9F10-D58D5CB99339}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0213FBF7-C06E-4E76-9F10-D58D5CB99339}.DebugFast|Any CPU.ActiveCfg = Debug|Any CPU + {0213FBF7-C06E-4E76-9F10-D58D5CB99339}.DebugFast|Any CPU.Build.0 = Debug|Any CPU + {0213FBF7-C06E-4E76-9F10-D58D5CB99339}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0213FBF7-C06E-4E76-9F10-D58D5CB99339}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/ArchiSteamFarm/AssemblyInfo.cs b/ArchiSteamFarm/AssemblyInfo.cs index 8af062bd9..ef96340b1 100644 --- a/ArchiSteamFarm/AssemblyInfo.cs +++ b/ArchiSteamFarm/AssemblyInfo.cs @@ -30,10 +30,12 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("ArchiSteamFarm.Tests, PublicKey=002400000480000014020000060200000024000052534131001000000100010099f0e5961ec7497fd7de1cba2b8c5eff3b18c1faf3d7a8d56e063359c7f928b54b14eae24d23d9d3c1a5db7ceca82edb6956d43e8ea2a0b7223e6e6836c0b809de43fde69bf33fba73cf669e71449284d477333d4b6e54fb69f7b6c4b4811b8fe26e88975e593cffc0e321490a50500865c01e50ab87c8a943b2a788af47dc20f2b860062b7b6df25477e471a744485a286b435cea2df3953cbb66febd8db73f3ccb4588886373141d200f749ba40bb11926b668cc15f328412dd0b0b835909229985336eb4a34f47925558dc6dc3910ea09c1aad5c744833f26ad9de727559d393526a7a29b3383de87802a034ead8ecc2d37340a5fa9b406774446256337d77e3c9e8486b5e732097e238312deaf5b4efcc04df8ecb986d90ee12b4a8a9a00319cc25cb91fd3e36a3cc39e501f83d14eb1e1a6fa6a1365483d99f4cefad1ea5dec204dad958e2a9a93add19781a8aa7bac71747b11d156711eafd1e873e19836eb573fa5cde284739df09b658ed40c56c7b5a7596840774a7065864e6c2af7b5a8bf7a2d238de83d77891d98ef5a4a58248c655a1c7c97c99e01d9928dc60c629eeb523356dc3686e3f9a1a30ffcd0268cd03718292f21d839fce741f4c1163001ab5b654c37d862998962a05e8028e061c611384772777ef6a49b00ebb4f228308e61b2afe408b33db2d82c4f385e26d7438ec0a183c64eeca4138cbc3dc2")] [assembly: InternalsVisibleTo("ArchiSteamFarm.OfficialPlugins.ItemsMatcher, PublicKey=002400000480000014020000060200000024000052534131001000000100010099f0e5961ec7497fd7de1cba2b8c5eff3b18c1faf3d7a8d56e063359c7f928b54b14eae24d23d9d3c1a5db7ceca82edb6956d43e8ea2a0b7223e6e6836c0b809de43fde69bf33fba73cf669e71449284d477333d4b6e54fb69f7b6c4b4811b8fe26e88975e593cffc0e321490a50500865c01e50ab87c8a943b2a788af47dc20f2b860062b7b6df25477e471a744485a286b435cea2df3953cbb66febd8db73f3ccb4588886373141d200f749ba40bb11926b668cc15f328412dd0b0b835909229985336eb4a34f47925558dc6dc3910ea09c1aad5c744833f26ad9de727559d393526a7a29b3383de87802a034ead8ecc2d37340a5fa9b406774446256337d77e3c9e8486b5e732097e238312deaf5b4efcc04df8ecb986d90ee12b4a8a9a00319cc25cb91fd3e36a3cc39e501f83d14eb1e1a6fa6a1365483d99f4cefad1ea5dec204dad958e2a9a93add19781a8aa7bac71747b11d156711eafd1e873e19836eb573fa5cde284739df09b658ed40c56c7b5a7596840774a7065864e6c2af7b5a8bf7a2d238de83d77891d98ef5a4a58248c655a1c7c97c99e01d9928dc60c629eeb523356dc3686e3f9a1a30ffcd0268cd03718292f21d839fce741f4c1163001ab5b654c37d862998962a05e8028e061c611384772777ef6a49b00ebb4f228308e61b2afe408b33db2d82c4f385e26d7438ec0a183c64eeca4138cbc3dc2")] [assembly: InternalsVisibleTo("ArchiSteamFarm.OfficialPlugins.MobileAuthenticator, PublicKey=002400000480000014020000060200000024000052534131001000000100010099f0e5961ec7497fd7de1cba2b8c5eff3b18c1faf3d7a8d56e063359c7f928b54b14eae24d23d9d3c1a5db7ceca82edb6956d43e8ea2a0b7223e6e6836c0b809de43fde69bf33fba73cf669e71449284d477333d4b6e54fb69f7b6c4b4811b8fe26e88975e593cffc0e321490a50500865c01e50ab87c8a943b2a788af47dc20f2b860062b7b6df25477e471a744485a286b435cea2df3953cbb66febd8db73f3ccb4588886373141d200f749ba40bb11926b668cc15f328412dd0b0b835909229985336eb4a34f47925558dc6dc3910ea09c1aad5c744833f26ad9de727559d393526a7a29b3383de87802a034ead8ecc2d37340a5fa9b406774446256337d77e3c9e8486b5e732097e238312deaf5b4efcc04df8ecb986d90ee12b4a8a9a00319cc25cb91fd3e36a3cc39e501f83d14eb1e1a6fa6a1365483d99f4cefad1ea5dec204dad958e2a9a93add19781a8aa7bac71747b11d156711eafd1e873e19836eb573fa5cde284739df09b658ed40c56c7b5a7596840774a7065864e6c2af7b5a8bf7a2d238de83d77891d98ef5a4a58248c655a1c7c97c99e01d9928dc60c629eeb523356dc3686e3f9a1a30ffcd0268cd03718292f21d839fce741f4c1163001ab5b654c37d862998962a05e8028e061c611384772777ef6a49b00ebb4f228308e61b2afe408b33db2d82c4f385e26d7438ec0a183c64eeca4138cbc3dc2")] +[assembly: InternalsVisibleTo("ArchiSteamFarm.OfficialPlugins.Monitoring, PublicKey=002400000480000014020000060200000024000052534131001000000100010099f0e5961ec7497fd7de1cba2b8c5eff3b18c1faf3d7a8d56e063359c7f928b54b14eae24d23d9d3c1a5db7ceca82edb6956d43e8ea2a0b7223e6e6836c0b809de43fde69bf33fba73cf669e71449284d477333d4b6e54fb69f7b6c4b4811b8fe26e88975e593cffc0e321490a50500865c01e50ab87c8a943b2a788af47dc20f2b860062b7b6df25477e471a744485a286b435cea2df3953cbb66febd8db73f3ccb4588886373141d200f749ba40bb11926b668cc15f328412dd0b0b835909229985336eb4a34f47925558dc6dc3910ea09c1aad5c744833f26ad9de727559d393526a7a29b3383de87802a034ead8ecc2d37340a5fa9b406774446256337d77e3c9e8486b5e732097e238312deaf5b4efcc04df8ecb986d90ee12b4a8a9a00319cc25cb91fd3e36a3cc39e501f83d14eb1e1a6fa6a1365483d99f4cefad1ea5dec204dad958e2a9a93add19781a8aa7bac71747b11d156711eafd1e873e19836eb573fa5cde284739df09b658ed40c56c7b5a7596840774a7065864e6c2af7b5a8bf7a2d238de83d77891d98ef5a4a58248c655a1c7c97c99e01d9928dc60c629eeb523356dc3686e3f9a1a30ffcd0268cd03718292f21d839fce741f4c1163001ab5b654c37d862998962a05e8028e061c611384772777ef6a49b00ebb4f228308e61b2afe408b33db2d82c4f385e26d7438ec0a183c64eeca4138cbc3dc2")] [assembly: InternalsVisibleTo("ArchiSteamFarm.OfficialPlugins.SteamTokenDumper, PublicKey=002400000480000014020000060200000024000052534131001000000100010099f0e5961ec7497fd7de1cba2b8c5eff3b18c1faf3d7a8d56e063359c7f928b54b14eae24d23d9d3c1a5db7ceca82edb6956d43e8ea2a0b7223e6e6836c0b809de43fde69bf33fba73cf669e71449284d477333d4b6e54fb69f7b6c4b4811b8fe26e88975e593cffc0e321490a50500865c01e50ab87c8a943b2a788af47dc20f2b860062b7b6df25477e471a744485a286b435cea2df3953cbb66febd8db73f3ccb4588886373141d200f749ba40bb11926b668cc15f328412dd0b0b835909229985336eb4a34f47925558dc6dc3910ea09c1aad5c744833f26ad9de727559d393526a7a29b3383de87802a034ead8ecc2d37340a5fa9b406774446256337d77e3c9e8486b5e732097e238312deaf5b4efcc04df8ecb986d90ee12b4a8a9a00319cc25cb91fd3e36a3cc39e501f83d14eb1e1a6fa6a1365483d99f4cefad1ea5dec204dad958e2a9a93add19781a8aa7bac71747b11d156711eafd1e873e19836eb573fa5cde284739df09b658ed40c56c7b5a7596840774a7065864e6c2af7b5a8bf7a2d238de83d77891d98ef5a4a58248c655a1c7c97c99e01d9928dc60c629eeb523356dc3686e3f9a1a30ffcd0268cd03718292f21d839fce741f4c1163001ab5b654c37d862998962a05e8028e061c611384772777ef6a49b00ebb4f228308e61b2afe408b33db2d82c4f385e26d7438ec0a183c64eeca4138cbc3dc2")] #else [assembly: InternalsVisibleTo("ArchiSteamFarm.Tests")] [assembly: InternalsVisibleTo("ArchiSteamFarm.OfficialPlugins.ItemsMatcher")] [assembly: InternalsVisibleTo("ArchiSteamFarm.OfficialPlugins.MobileAuthenticator")] +[assembly: InternalsVisibleTo("ArchiSteamFarm.OfficialPlugins.Monitoring")] [assembly: InternalsVisibleTo("ArchiSteamFarm.OfficialPlugins.SteamTokenDumper")] #endif diff --git a/ArchiSteamFarm/IPC/ArchiKestrel.cs b/ArchiSteamFarm/IPC/ArchiKestrel.cs index 8bc1f224b..18813417d 100644 --- a/ArchiSteamFarm/IPC/ArchiKestrel.cs +++ b/ArchiSteamFarm/IPC/ArchiKestrel.cs @@ -230,6 +230,16 @@ internal static class ArchiKestrel { // Finally register proper API endpoints once we're done with routing app.UseEndpoints(static endpoints => endpoints.MapControllers()); + if (PluginsCore.ActivePlugins.Count > 0) { + foreach (IWebServiceProvider plugin in PluginsCore.ActivePlugins.OfType()) { + try { + plugin.OnConfiguringEndpoints(app); + } catch (Exception e) { + ASF.ArchiLogger.LogGenericException(e); + } + } + } + // Add support for swagger, responsible for automatic API documentation generation, this should be on the end, once we're done with API app.UseSwagger(); @@ -379,6 +389,14 @@ internal static class ArchiKestrel { mvc.AddApplicationPart(assembly); } } + + foreach (IWebServiceProvider plugin in PluginsCore.ActivePlugins.OfType()) { + try { + plugin.OnConfiguringServices(services); + } catch (Exception e) { + ASF.ArchiLogger.LogGenericException(e); + } + } } mvc.AddControllersAsServices(); diff --git a/ArchiSteamFarm/Plugins/Interfaces/IWebServiceProvider.cs b/ArchiSteamFarm/Plugins/Interfaces/IWebServiceProvider.cs new file mode 100644 index 000000000..ac82d3b21 --- /dev/null +++ b/ArchiSteamFarm/Plugins/Interfaces/IWebServiceProvider.cs @@ -0,0 +1,46 @@ +// ---------------------------------------------------------------------------------------------- +// _ _ _ ____ _ _____ +// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___ +// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \ +// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | +// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| +// ---------------------------------------------------------------------------------------------- +// | +// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki +// Contact: JustArchi@JustArchi.net +// | +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// | +// http://www.apache.org/licenses/LICENSE-2.0 +// | +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using JetBrains.Annotations; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; + +namespace ArchiSteamFarm.Plugins.Interfaces; + +/// +/// Implementing this interface allows you to provide your own additional services and middlewares for IPC endpoints - Use with caution! +/// +[PublicAPI] +public interface IWebServiceProvider { + /// + /// ASF will call this method during configuration of the IPC endpoints + /// + /// Application builder related to this callback. + void OnConfiguringEndpoints(IApplicationBuilder app); + + /// + /// ASF will call this method during configuration of the IPC services + /// + /// Service collection related to this callback. + void OnConfiguringServices(IServiceCollection services); +} diff --git a/ArchiSteamFarm/Plugins/PluginsCore.cs b/ArchiSteamFarm/Plugins/PluginsCore.cs index 0a9ae91a9..e91b1665b 100644 --- a/ArchiSteamFarm/Plugins/PluginsCore.cs +++ b/ArchiSteamFarm/Plugins/PluginsCore.cs @@ -55,6 +55,9 @@ using SteamKit2; namespace ArchiSteamFarm.Plugins; public static class PluginsCore { + [PublicAPI] + public static int ActivePluginsCount => ActivePlugins.Count; + internal static bool HasCustomPluginsLoaded => ActivePlugins.Any(static plugin => plugin is not OfficialPlugin officialPlugin || !officialPlugin.HasSameVersion()); [ImportMany] diff --git a/Directory.Packages.props b/Directory.Packages.props index 54c51635b..e122c0b04 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -10,6 +10,11 @@ + + + + +