diff --git a/ArchiSteamFarm/Actions.cs b/ArchiSteamFarm/Actions.cs index 7da817929..963f2b671 100644 --- a/ArchiSteamFarm/Actions.cs +++ b/ArchiSteamFarm/Actions.cs @@ -25,6 +25,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using ArchiSteamFarm.Collections; +using ArchiSteamFarm.Helpers; using ArchiSteamFarm.Json; using ArchiSteamFarm.Localization; using SteamKit2; @@ -36,20 +37,19 @@ namespace ArchiSteamFarm { private readonly Bot Bot; private readonly ConcurrentHashSet HandledGifts = new ConcurrentHashSet(); - private readonly SemaphoreSlim LootingSemaphore = new SemaphoreSlim(1, 1); + private readonly SemaphoreSlim TradingSemaphore = new SemaphoreSlim(1, 1); internal bool SkipFirstShutdown { get; set; } private Timer CardsFarmerResumeTimer; - private bool LootingAllowed = true; - private bool LootingScheduled; private bool ProcessingGiftsScheduled; + private bool TradingScheduled; internal Actions(Bot bot) => Bot = bot ?? throw new ArgumentNullException(nameof(bot)); public void Dispose() { // Those are objects that are always being created if constructor doesn't throw exception - LootingSemaphore.Dispose(); + TradingSemaphore.Dispose(); // Those are objects that might be null and the check should be in-place CardsFarmerResumeTimer?.Dispose(); @@ -172,6 +172,11 @@ namespace ArchiSteamFarm { return (true, Strings.Done); } + internal async Task GetTradingLock() { + await TradingSemaphore.WaitAsync().ConfigureAwait(false); + return new SemaphoreLock(TradingSemaphore); + } + internal void OnDisconnected() => HandledGifts.Clear(); internal async Task<(bool Success, string Output)> Pause(bool permanent, ushort resumeInSeconds = 0) { @@ -251,14 +256,6 @@ namespace ArchiSteamFarm { return (false, Strings.BotNotConnected); } - if (!LootingAllowed) { - return (false, Strings.BotLootingTemporarilyDisabled); - } - - if (Bot.BotConfig.LootableTypes.Count == 0) { - return (false, Strings.BotLootingNoLootableTypes); - } - if (targetSteamID == 0) { targetSteamID = GetFirstSteamMasterID(); if (targetSteamID == 0) { @@ -270,19 +267,19 @@ namespace ArchiSteamFarm { return (false, Strings.BotSendingTradeToYourself); } - lock (LootingSemaphore) { - if (LootingScheduled) { - return (false, Strings.BotLootingTemporarilyDisabled); + lock (TradingSemaphore) { + if (TradingScheduled) { + return (false, Strings.ErrorAborted); } - LootingScheduled = true; + TradingScheduled = true; } - await LootingSemaphore.WaitAsync().ConfigureAwait(false); + await TradingSemaphore.WaitAsync().ConfigureAwait(false); try { - lock (LootingSemaphore) { - LootingScheduled = false; + lock (TradingSemaphore) { + TradingScheduled = false; } HashSet inventory = await Bot.ArchiWebHandler.GetInventory(Bot.SteamID, appID, contextID, true, wantedTypes, wantedRealAppIDs).ConfigureAwait(false); @@ -306,7 +303,7 @@ namespace ArchiSteamFarm { return (false, Strings.BotLootingFailed); } } finally { - LootingSemaphore.Release(); + TradingSemaphore.Release(); } return (true, Strings.BotLootingSuccess); @@ -331,8 +328,6 @@ namespace ArchiSteamFarm { return (true, Strings.Done); } - internal bool SwitchLootingAllowed() => LootingAllowed = !LootingAllowed; - internal static async Task<(bool Success, string Message)> Update() { Version version = await ASF.Update(true).ConfigureAwait(false); if (version == null) { diff --git a/ArchiSteamFarm/Commands.cs b/ArchiSteamFarm/Commands.cs index 0773f5b16..c54512db8 100644 --- a/ArchiSteamFarm/Commands.cs +++ b/ArchiSteamFarm/Commands.cs @@ -89,8 +89,6 @@ namespace ArchiSteamFarm { return await ResponseLevel(steamID).ConfigureAwait(false); case "LOOT": return await ResponseLoot(steamID).ConfigureAwait(false); - case "LOOT&": - return ResponseLootSwitch(steamID); case "PASSWORD": return ResponsePassword(steamID); case "PAUSE": @@ -182,8 +180,6 @@ namespace ArchiSteamFarm { return await ResponseLootByRealAppIDs(steamID, args[1], Utilities.GetArgsAsText(args, 2, ",")).ConfigureAwait(false); case "LOOT@": return await ResponseLootByRealAppIDs(steamID, args[1]).ConfigureAwait(false); - case "LOOT&": - return await ResponseLootSwitch(steamID, Utilities.GetArgsAsText(args, 1, ",")).ConfigureAwait(false); case "NICKNAME" when args.Length > 2: return await ResponseNickname(steamID, args[1], Utilities.GetArgsAsText(message, 2)).ConfigureAwait(false); case "NICKNAME": @@ -1164,6 +1160,10 @@ namespace ArchiSteamFarm { return FormatBotResponse(Strings.BotNotConnected); } + if (Bot.BotConfig.LootableTypes.Count == 0) { + return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(Bot.BotConfig.LootableTypes))); + } + (bool success, string output) = await Bot.Actions.SendTradeOffer(wantedTypes: Bot.BotConfig.LootableTypes).ConfigureAwait(false); return FormatBotResponse(success ? output : string.Format(Strings.WarningFailedWithError, output)); } @@ -1199,6 +1199,10 @@ namespace ArchiSteamFarm { return FormatBotResponse(Strings.BotNotConnected); } + if (Bot.BotConfig.LootableTypes.Count == 0) { + return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(Bot.BotConfig.LootableTypes))); + } + string[] appIDTexts = realAppIDsText.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); if (appIDTexts.Length == 0) { @@ -1236,37 +1240,6 @@ namespace ArchiSteamFarm { return responses.Count > 0 ? string.Join(Environment.NewLine, responses) : null; } - private string ResponseLootSwitch(ulong steamID) { - if (steamID == 0) { - Bot.ArchiLogger.LogNullError(nameof(steamID)); - return null; - } - - if (!Bot.IsMaster(steamID)) { - return null; - } - - bool newValue = Bot.Actions.SwitchLootingAllowed(); - return FormatBotResponse(newValue ? Strings.BotLootingNowEnabled : Strings.BotLootingNowDisabled); - } - - private static async Task ResponseLootSwitch(ulong steamID, string botNames) { - if ((steamID == 0) || string.IsNullOrEmpty(botNames)) { - ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames)); - return null; - } - - HashSet bots = Bot.GetBots(botNames); - if ((bots == null) || (bots.Count == 0)) { - return ASF.IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null; - } - - IList results = await Utilities.InParallel(bots.Select(bot => Task.Run(() => bot.Commands.ResponseLootSwitch(steamID)))).ConfigureAwait(false); - - List responses = new List(results.Where(result => !string.IsNullOrEmpty(result))); - return responses.Count > 0 ? string.Join(Environment.NewLine, responses) : null; - } - private string ResponseNickname(ulong steamID, string nickname) { if ((steamID == 0) || string.IsNullOrEmpty(nickname)) { Bot.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(nickname)); @@ -2108,6 +2081,10 @@ namespace ArchiSteamFarm { return FormatBotResponse(Strings.BotNotConnected); } + if (Bot.BotConfig.TransferableTypes.Count == 0) { + return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(Bot.BotConfig.TransferableTypes))); + } + if (!Bot.Bots.TryGetValue(botNameTo, out Bot targetBot)) { return ASF.IsOwner(steamID) ? FormatBotResponse(string.Format(Strings.BotNotFound, botNameTo)) : null; } @@ -2141,7 +2118,7 @@ namespace ArchiSteamFarm { return responses.Count > 0 ? string.Join(Environment.NewLine, responses) : null; } - private async Task ResponseTransferByRealAppIDs(ulong steamID, HashSet realAppIDs, Bot targetBot) { + private async Task ResponseTransferByRealAppIDs(ulong steamID, IReadOnlyCollection realAppIDs, Bot targetBot) { if ((steamID == 0) || (realAppIDs == null) || (realAppIDs.Count == 0) || (targetBot == null)) { Bot.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(realAppIDs) + " || " + nameof(targetBot)); return null; @@ -2155,6 +2132,10 @@ namespace ArchiSteamFarm { return FormatBotResponse(Strings.BotNotConnected); } + if (Bot.BotConfig.TransferableTypes.Count == 0) { + return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(Bot.BotConfig.TransferableTypes))); + } + if (!targetBot.IsConnectedAndLoggedOn) { return FormatBotResponse(Strings.TargetBotNotConnected); } diff --git a/ArchiSteamFarm/Helpers/SemaphoreLock.cs b/ArchiSteamFarm/Helpers/SemaphoreLock.cs new file mode 100644 index 000000000..c95f10a65 --- /dev/null +++ b/ArchiSteamFarm/Helpers/SemaphoreLock.cs @@ -0,0 +1,33 @@ +// _ _ _ ____ _ _____ +// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___ +// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \ +// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | +// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| +// +// Copyright 2015-2018 Ł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.Threading; + +namespace ArchiSteamFarm.Helpers { + internal sealed class SemaphoreLock : IDisposable { + private readonly SemaphoreSlim Semaphore; + + internal SemaphoreLock(SemaphoreSlim semaphore) => Semaphore = semaphore ?? throw new ArgumentNullException(nameof(semaphore)); + + public void Dispose() => Semaphore.Release(); + } +} diff --git a/ArchiSteamFarm/Localization/Strings.Designer.cs b/ArchiSteamFarm/Localization/Strings.Designer.cs index bbf96b581..6d81d6d50 100644 --- a/ArchiSteamFarm/Localization/Strings.Designer.cs +++ b/ArchiSteamFarm/Localization/Strings.Designer.cs @@ -411,33 +411,6 @@ namespace ArchiSteamFarm.Localization { } } - /// - /// Wyszukuje zlokalizowany ciąg podobny do ciągu You don't have any lootable types set!. - /// - internal static string BotLootingNoLootableTypes { - get { - return ResourceManager.GetString("BotLootingNoLootableTypes", resourceCulture); - } - } - - /// - /// Wyszukuje zlokalizowany ciąg podobny do ciągu Looting is now disabled!. - /// - internal static string BotLootingNowDisabled { - get { - return ResourceManager.GetString("BotLootingNowDisabled", resourceCulture); - } - } - - /// - /// Wyszukuje zlokalizowany ciąg podobny do ciągu Looting is now enabled!. - /// - internal static string BotLootingNowEnabled { - get { - return ResourceManager.GetString("BotLootingNowEnabled", resourceCulture); - } - } - /// /// Wyszukuje zlokalizowany ciąg podobny do ciągu Trade offer sent successfully!. /// @@ -447,15 +420,6 @@ namespace ArchiSteamFarm.Localization { } } - /// - /// Wyszukuje zlokalizowany ciąg podobny do ciągu Looting is temporarily disabled!. - /// - internal static string BotLootingTemporarilyDisabled { - get { - return ResourceManager.GetString("BotLootingTemporarilyDisabled", resourceCulture); - } - } - /// /// Wyszukuje zlokalizowany ciąg podobny do ciągu This bot doesn't have ASF 2FA enabled! Did you forget to import your authenticator as ASF 2FA?. /// diff --git a/ArchiSteamFarm/Localization/Strings.resx b/ArchiSteamFarm/Localization/Strings.resx index cc5611dbd..726f1debb 100644 --- a/ArchiSteamFarm/Localization/Strings.resx +++ b/ArchiSteamFarm/Localization/Strings.resx @@ -466,21 +466,9 @@ StackTrace: Trade couldn't be sent because there is no user with master permission defined! - - You don't have any lootable types set! - - - Looting is now disabled! - - - Looting is now enabled! - Trade offer sent successfully! - - Looting is temporarily disabled! - You can't send trade to yourself! diff --git a/ArchiSteamFarm/Statistics.cs b/ArchiSteamFarm/Statistics.cs index ae308679c..fd0a10c56 100644 --- a/ArchiSteamFarm/Statistics.cs +++ b/ArchiSteamFarm/Statistics.cs @@ -239,9 +239,11 @@ namespace ArchiSteamFarm { break; } - Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.ActivelyMatchingItems, i)); - match = await MatchActivelyRound(acceptedMatchableTypes).ConfigureAwait(false); - Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.DoneActivelyMatchingItems, i)); + using (await Bot.Actions.GetTradingLock().ConfigureAwait(false)) { + Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.ActivelyMatchingItems, i)); + match = await MatchActivelyRound(acceptedMatchableTypes).ConfigureAwait(false); + Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.DoneActivelyMatchingItems, i)); + } } Bot.ArchiLogger.LogGenericTrace(Strings.Done);