diff --git a/ArchiSteamFarm/Bot.cs b/ArchiSteamFarm/Bot.cs index 8533aaa98..c67fdbbb8 100755 --- a/ArchiSteamFarm/Bot.cs +++ b/ArchiSteamFarm/Bot.cs @@ -982,9 +982,14 @@ namespace ArchiSteamFarm { if (BotConfig.ShutdownOnFarmingFinished) { Stop(); } + + await PluginsCore.OnBotFarmingFinished(this, farmedSomething).ConfigureAwait(false); } - internal async Task OnFarmingStopped() => await ResetGamesPlayed().ConfigureAwait(false); + internal async Task OnFarmingStopped() { + await ResetGamesPlayed().ConfigureAwait(false); + await PluginsCore.OnBotFarmingStopped(this).ConfigureAwait(false); + } internal async Task RefreshSession() { if (!IsConnectedAndLoggedOn) { diff --git a/ArchiSteamFarm/CardsFarmer.cs b/ArchiSteamFarm/CardsFarmer.cs index fed62c39c..f3be66988 100755 --- a/ArchiSteamFarm/CardsFarmer.cs +++ b/ArchiSteamFarm/CardsFarmer.cs @@ -31,6 +31,7 @@ using System.Threading; using System.Threading.Tasks; using ArchiSteamFarm.Collections; using ArchiSteamFarm.Localization; +using ArchiSteamFarm.Plugins; using HtmlAgilityPack; using JetBrains.Annotations; using Newtonsoft.Json; @@ -52,20 +53,26 @@ namespace ArchiSteamFarm { // Games that were confirmed to show false status on general badges page private static readonly ImmutableHashSet UntrustedAppIDs = ImmutableHashSet.Create(440, 570, 730); - [JsonProperty] - internal readonly ConcurrentHashSet CurrentGamesFarming = new ConcurrentHashSet(); + [JsonProperty(PropertyName = "CurrentGamesFarming")] + [PublicAPI] + public IReadOnlyCollection CurrentGamesFarmingReadOnly => CurrentGamesFarming; + + [JsonProperty(PropertyName = "GamesToFarm")] + [PublicAPI] + public IReadOnlyCollection GamesToFarmReadOnly => GamesToFarm; [JsonProperty] - internal readonly ConcurrentList GamesToFarm = new ConcurrentList(); - - [JsonProperty] - internal TimeSpan TimeRemaining => + [PublicAPI] + public TimeSpan TimeRemaining => new TimeSpan( Bot.BotConfig.HoursUntilCardDrops > 0 ? (ushort) Math.Ceiling(GamesToFarm.Count / (float) ArchiHandler.MaxGamesPlayedConcurrently) * Bot.BotConfig.HoursUntilCardDrops : 0, 30 * GamesToFarm.Sum(game => game.CardsRemaining), 0 ); + internal readonly ConcurrentHashSet CurrentGamesFarming = new ConcurrentHashSet(); + internal readonly ConcurrentList GamesToFarm = new ConcurrentList(); + private readonly Bot Bot; private readonly SemaphoreSlim EventSemaphore = new SemaphoreSlim(1, 1); private readonly SemaphoreSlim FarmingInitializationSemaphore = new SemaphoreSlim(1, 1); @@ -80,10 +87,11 @@ namespace ArchiSteamFarm { } } - internal bool NowFarming { get; private set; } - [JsonProperty] - internal bool Paused { get; private set; } + [PublicAPI] + public bool Paused { get; private set; } + + internal bool NowFarming { get; private set; } private bool KeepFarming; private bool ParsingScheduled; @@ -293,6 +301,8 @@ namespace ArchiSteamFarm { KeepFarming = NowFarming = true; Utilities.InBackground(Farm, true); + + await PluginsCore.OnBotFarmingStarted(Bot).ConfigureAwait(false); } finally { FarmingInitializationSemaphore.Release(); } @@ -1207,20 +1217,20 @@ namespace ArchiSteamFarm { GamesToFarm.ReplaceWith(gamesToFarm); } - internal sealed class Game : IEquatable { + public sealed class Game : IEquatable { [JsonProperty] - internal readonly uint AppID; + public readonly uint AppID; + + [JsonProperty] + public readonly string GameName; internal readonly byte BadgeLevel; [JsonProperty] - internal readonly string GameName; + public ushort CardsRemaining { get; internal set; } [JsonProperty] - internal ushort CardsRemaining { get; set; } - - [JsonProperty] - internal float HoursPlayed { get; set; } + public float HoursPlayed { get; internal set; } internal uint PlayableAppID { get; set; } diff --git a/ArchiSteamFarm/Plugins/IBotCardsFarmerInfo.cs b/ArchiSteamFarm/Plugins/IBotCardsFarmerInfo.cs new file mode 100644 index 000000000..9dfab0925 --- /dev/null +++ b/ArchiSteamFarm/Plugins/IBotCardsFarmerInfo.cs @@ -0,0 +1,46 @@ +// _ _ _ ____ _ _____ +// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___ +// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \ +// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | +// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| +// | +// Copyright 2015-2019 Ɓ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; + +namespace ArchiSteamFarm.Plugins { + [PublicAPI] + public interface IBotCardsFarmerInfo : IPlugin { + /// + /// ASF will call this method when cards farming module is finished on given bot instance. This method will also be called when there is nothing to idle or idling is unavailable, you can use provided boolean value for determining that. + /// + /// Bot object related to this callback. + /// Bool value indicating whether the module has confirmed to drop at least one card during the process. + void OnBotFarmingFinished(Bot bot, bool farmedSomething); + + /// + /// ASF will call this method when cards farming module is started on given bot instance. The module is started only when there are valid cards to drop, so this method won't be called when there is nothing to idle. + /// + /// Bot object related to this callback. + void OnBotFarmingStarted(Bot bot); + + /// + /// ASF will call this method when cards farming module is stopped on given bot instance. The stop could be a result of a natural finish, or other situations (e.g. Steam networking issues, user commands). + /// + /// Bot object related to this callback. + void OnBotFarmingStopped(Bot bot); + } +} diff --git a/ArchiSteamFarm/Plugins/PluginsCore.cs b/ArchiSteamFarm/Plugins/PluginsCore.cs index 59a14daec..57321afc3 100644 --- a/ArchiSteamFarm/Plugins/PluginsCore.cs +++ b/ArchiSteamFarm/Plugins/PluginsCore.cs @@ -234,6 +234,60 @@ namespace ArchiSteamFarm.Plugins { } } + internal static async Task OnBotFarmingStarted(Bot bot) { + if (bot == null) { + ASF.ArchiLogger.LogNullError(nameof(bot)); + + return; + } + + if (!HasActivePluginsLoaded) { + return; + } + + try { + await Utilities.InParallel(ActivePlugins.OfType().Select(plugin => Task.Run(() => plugin.OnBotFarmingStarted(bot)))).ConfigureAwait(false); + } catch (Exception e) { + ASF.ArchiLogger.LogGenericException(e); + } + } + + internal static async Task OnBotFarmingStopped(Bot bot) { + if (bot == null) { + ASF.ArchiLogger.LogNullError(nameof(bot)); + + return; + } + + if (!HasActivePluginsLoaded) { + return; + } + + try { + await Utilities.InParallel(ActivePlugins.OfType().Select(plugin => Task.Run(() => plugin.OnBotFarmingStopped(bot)))).ConfigureAwait(false); + } catch (Exception e) { + ASF.ArchiLogger.LogGenericException(e); + } + } + + internal static async Task OnBotFarmingFinished(Bot bot, bool farmedSomething) { + if (bot == null) { + ASF.ArchiLogger.LogNullError(nameof(bot)); + + return; + } + + if (!HasActivePluginsLoaded) { + return; + } + + try { + await Utilities.InParallel(ActivePlugins.OfType().Select(plugin => Task.Run(() => plugin.OnBotFarmingFinished(bot, farmedSomething)))).ConfigureAwait(false); + } catch (Exception e) { + ASF.ArchiLogger.LogGenericException(e); + } + } + internal static async Task OnBotFriendRequest(Bot bot, ulong steamID) { if ((bot == null) || (steamID == 0)) { ASF.ArchiLogger.LogNullError(nameof(bot) + " || " + nameof(steamID));