diff --git a/ArchiSteamFarm.OfficialPlugins.Monitoring/ArchiSteamFarm.OfficialPlugins.Monitoring.csproj b/ArchiSteamFarm.OfficialPlugins.Monitoring/ArchiSteamFarm.OfficialPlugins.Monitoring.csproj
new file mode 100644
index 0000000000000..f4f3195be8a9d
--- /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 0000000000000..370744c5e7496
--- /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 0000000000000..e1ff7e97dd96a
--- /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 0000000000000..bbe3ea1543cb5
--- /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 441a15454967d..a23907a513ddc 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 8af062bd922b3..ef96340b112c2 100644
--- a/ArchiSteamFarm/AssemblyInfo.cs
+++ b/ArchiSteamFarm/AssemblyInfo.cs
@@ -30,10 +30,12 @@
[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 8bc1f224b978f..18813417d74f9 100644
--- a/ArchiSteamFarm/IPC/ArchiKestrel.cs
+++ b/ArchiSteamFarm/IPC/ArchiKestrel.cs
@@ -230,6 +230,16 @@ private static void ConfigureApp([SuppressMessage("ReSharper", "SuggestBaseTypeF
// 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 @@ private static void ConfigureServices([SuppressMessage("ReSharper", "SuggestBase
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 0000000000000..ac82d3b218028
--- /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 0a9ae91a99472..e91b1665b5ee0 100644
--- a/ArchiSteamFarm/Plugins/PluginsCore.cs
+++ b/ArchiSteamFarm/Plugins/PluginsCore.cs
@@ -55,6 +55,9 @@
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 54c51635b6613..e122c0b04161e 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -10,6 +10,11 @@
+
+
+
+
+