From 4f5d46b422e4cb8db4a0f5a9568836c424111207 Mon Sep 17 00:00:00 2001 From: JustArchi Date: Tue, 28 Mar 2017 21:27:01 +0200 Subject: [PATCH] Closes #515 --- ArchiSteamFarm/Bot.cs | 187 ++++++++++++++++++++++++++++ ArchiSteamFarm/BotDatabase.cs | 37 ++++++ ArchiSteamFarm/ConcurrentHashSet.cs | 81 +++++++++++- ArchiSteamFarm/Trading.cs | 13 +- 4 files changed, 312 insertions(+), 6 deletions(-) diff --git a/ArchiSteamFarm/Bot.cs b/ArchiSteamFarm/Bot.cs index e6c8b21d5..824c7fa75 100755 --- a/ArchiSteamFarm/Bot.cs +++ b/ArchiSteamFarm/Bot.cs @@ -459,6 +459,15 @@ namespace ArchiSteamFarm { } } + internal bool IsBlacklistedFromTrades(ulong steamID) { + if (steamID != 0) { + return BotDatabase.IsBlacklistedFromTrades(steamID); + } + + ArchiLogger.LogNullError(nameof(steamID)); + return false; + } + internal bool IsMaster(ulong steamID) { if (steamID == 0) { ArchiLogger.LogNullError(nameof(steamID)); @@ -607,6 +616,8 @@ namespace ArchiSteamFarm { return await Response2FAConfirm(steamID, true).ConfigureAwait(false); case "!API": return ResponseAPI(steamID); + case "!BL": + return ResponseBlacklist(steamID); case "!EXIT": return ResponseExit(steamID); case "!FARM": @@ -660,6 +671,20 @@ namespace ArchiSteamFarm { return await ResponseAddLicense(steamID, args[1]).ConfigureAwait(false); case "!API": return ResponseAPI(steamID, args[1]); + case "!BL": + return await ResponseBlacklist(steamID, args[1]).ConfigureAwait(false); + case "!BLADD": + if (args.Length > 2) { + return await ResponseBlacklistAdd(steamID, args[1], args[2]).ConfigureAwait(false); + } + + return ResponseBlacklistAdd(steamID, args[1]); + case "!BLRM": + if (args.Length > 2) { + return await ResponseBlacklistRemove(steamID, args[1], args[2]).ConfigureAwait(false); + } + + return ResponseBlacklistRemove(steamID, args[1]); case "!FARM": return await ResponseFarm(steamID, args[1]).ConfigureAwait(false); case "!INPUT": @@ -2108,6 +2133,168 @@ namespace ArchiSteamFarm { return GetAPIStatus(Bots.Where(kv => bots.Contains(kv.Value) && kv.Value.IsMaster(steamID)).ToDictionary(kv => kv.Key, kv => kv.Value)); } + private static async Task ResponseBlacklist(ulong steamID, string botNames) { + if ((steamID == 0) || string.IsNullOrEmpty(botNames)) { + ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames)); + return null; + } + + HashSet bots = GetBots(botNames); + if ((bots == null) || (bots.Count == 0)) { + return IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null; + } + + ICollection results; + IEnumerable> tasks = bots.Select(bot => Task.Run(() => bot.ResponseBlacklist(steamID))); + + switch (Program.GlobalConfig.OptimizationMode) { + case GlobalConfig.EOptimizationMode.MinMemoryUsage: + results = new List(bots.Count); + foreach (Task task in tasks) { + results.Add(await task.ConfigureAwait(false)); + } + + break; + default: + results = await Task.WhenAll(tasks).ConfigureAwait(false); + break; + } + + List responses = new List(results.Where(result => !string.IsNullOrEmpty(result))); + return responses.Count > 0 ? string.Join("", responses) : null; + } + + private string ResponseBlacklist(ulong steamID) { + if (steamID != 0) { + return IsMaster(steamID) ? FormatBotResponse(string.Join(", ", BotDatabase.GetBlacklistedFromTradesSteamIDs())) : null; + } + + ArchiLogger.LogNullError(nameof(steamID)); + return null; + } + + private string ResponseBlacklistAdd(ulong steamID, string targetsText) { + if ((steamID == 0) || string.IsNullOrEmpty(targetsText)) { + ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(targetsText)); + return null; + } + + if (!IsMaster(steamID)) { + return null; + } + + string[] targets = targetsText.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + + HashSet targetIDs = new HashSet(); + foreach (string target in targets) { + ulong targetID; + if (!ulong.TryParse(target, out targetID) || (targetID == 0)) { + return FormatBotResponse(string.Format(Strings.ErrorParsingObject, nameof(targetID))); + } + + targetIDs.Add(targetID); + } + + if (targetIDs.Count == 0) { + return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(targetIDs))); + } + + BotDatabase.AddBlacklistedFromTradesSteamIDs(targetIDs); + return FormatBotResponse(Strings.Done); + } + + private static async Task ResponseBlacklistAdd(ulong steamID, string botNames, string targetsText) { + if ((steamID == 0) || string.IsNullOrEmpty(botNames) || string.IsNullOrEmpty(targetsText)) { + ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames) + " || " + nameof(targetsText)); + return null; + } + + HashSet bots = GetBots(botNames); + if ((bots == null) || (bots.Count == 0)) { + return IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null; + } + + ICollection results; + IEnumerable> tasks = bots.Select(bot => Task.Run(() => bot.ResponseBlacklistAdd(steamID, targetsText))); + + switch (Program.GlobalConfig.OptimizationMode) { + case GlobalConfig.EOptimizationMode.MinMemoryUsage: + results = new List(bots.Count); + foreach (Task task in tasks) { + results.Add(await task.ConfigureAwait(false)); + } + + break; + default: + results = await Task.WhenAll(tasks).ConfigureAwait(false); + break; + } + + List responses = new List(results.Where(result => !string.IsNullOrEmpty(result))); + return responses.Count > 0 ? string.Join("", responses) : null; + } + + private static async Task ResponseBlacklistRemove(ulong steamID, string botNames, string targetsText) { + if ((steamID == 0) || string.IsNullOrEmpty(botNames) || string.IsNullOrEmpty(targetsText)) { + ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames) + " || " + nameof(targetsText)); + return null; + } + + HashSet bots = GetBots(botNames); + if ((bots == null) || (bots.Count == 0)) { + return IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null; + } + + ICollection results; + IEnumerable> tasks = bots.Select(bot => Task.Run(() => bot.ResponseBlacklistRemove(steamID, targetsText))); + + switch (Program.GlobalConfig.OptimizationMode) { + case GlobalConfig.EOptimizationMode.MinMemoryUsage: + results = new List(bots.Count); + foreach (Task task in tasks) { + results.Add(await task.ConfigureAwait(false)); + } + + break; + default: + results = await Task.WhenAll(tasks).ConfigureAwait(false); + break; + } + + List responses = new List(results.Where(result => !string.IsNullOrEmpty(result))); + return responses.Count > 0 ? string.Join("", responses) : null; + } + + private string ResponseBlacklistRemove(ulong steamID, string targetsText) { + if ((steamID == 0) || string.IsNullOrEmpty(targetsText)) { + ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(targetsText)); + return null; + } + + if (!IsMaster(steamID)) { + return null; + } + + string[] targets = targetsText.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + + HashSet targetIDs = new HashSet(); + foreach (string target in targets) { + ulong targetID; + if (!ulong.TryParse(target, out targetID) || (targetID == 0)) { + return FormatBotResponse(string.Format(Strings.ErrorParsingObject, nameof(targetID))); + } + + targetIDs.Add(targetID); + } + + if (targetIDs.Count == 0) { + return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(targetIDs))); + } + + BotDatabase.RemoveBlacklistedFromTradesSteamIDs(targetIDs); + return FormatBotResponse(Strings.Done); + } + private static string ResponseExit(ulong steamID) { if (steamID == 0) { ASF.ArchiLogger.LogNullError(nameof(steamID)); diff --git a/ArchiSteamFarm/BotDatabase.cs b/ArchiSteamFarm/BotDatabase.cs index d18de0a44..6b7433aee 100644 --- a/ArchiSteamFarm/BotDatabase.cs +++ b/ArchiSteamFarm/BotDatabase.cs @@ -23,6 +23,7 @@ */ using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Threading; @@ -30,6 +31,9 @@ using Newtonsoft.Json; namespace ArchiSteamFarm { internal sealed class BotDatabase { + [JsonProperty(Required = Required.DisallowNull)] + private readonly ConcurrentHashSet BlacklistedFromTradesSteamIDs = new ConcurrentHashSet(); + private readonly object FileLock = new object(); internal string LoginKey { @@ -80,6 +84,28 @@ namespace ArchiSteamFarm { [SuppressMessage("ReSharper", "UnusedMember.Local")] private BotDatabase() { } + internal void AddBlacklistedFromTradesSteamIDs(HashSet steamIDs) { + if ((steamIDs == null) || (steamIDs.Count == 0)) { + ASF.ArchiLogger.LogNullError(nameof(steamIDs)); + return; + } + + if (BlacklistedFromTradesSteamIDs.AddRange(steamIDs)) { + Save(); + } + } + + internal IEnumerable GetBlacklistedFromTradesSteamIDs() => BlacklistedFromTradesSteamIDs; + + internal bool IsBlacklistedFromTrades(ulong steamID) { + if (steamID != 0) { + return BlacklistedFromTradesSteamIDs.Contains(steamID); + } + + ASF.ArchiLogger.LogNullError(nameof(steamID)); + return false; + } + internal static BotDatabase Load(string filePath) { if (string.IsNullOrEmpty(filePath)) { ASF.ArchiLogger.LogNullError(nameof(filePath)); @@ -108,6 +134,17 @@ namespace ArchiSteamFarm { return botDatabase; } + internal void RemoveBlacklistedFromTradesSteamIDs(HashSet steamIDs) { + if ((steamIDs == null) || (steamIDs.Count == 0)) { + ASF.ArchiLogger.LogNullError(nameof(steamIDs)); + return; + } + + if (BlacklistedFromTradesSteamIDs.RemoveRange(steamIDs)) { + Save(); + } + } + internal void Save() { string json = JsonConvert.SerializeObject(this); if (string.IsNullOrEmpty(json)) { diff --git a/ArchiSteamFarm/ConcurrentHashSet.cs b/ArchiSteamFarm/ConcurrentHashSet.cs index 39605ae36..e451b27c3 100644 --- a/ArchiSteamFarm/ConcurrentHashSet.cs +++ b/ArchiSteamFarm/ConcurrentHashSet.cs @@ -24,10 +24,11 @@ using System.Collections; using System.Collections.Generic; +using System.Linq; using Nito.AsyncEx; namespace ArchiSteamFarm { - internal sealed class ConcurrentHashSet : ICollection { + internal sealed class ConcurrentHashSet : IReadOnlyCollection, ISet { public int Count { get { using (Lock.ReaderLock()) { @@ -41,6 +42,12 @@ namespace ArchiSteamFarm { private readonly HashSet HashSet = new HashSet(); private readonly AsyncReaderWriterLock Lock = new AsyncReaderWriterLock(); + public bool Add(T item) { + using (Lock.WriterLock()) { + return HashSet.Add(item); + } + } + public void Clear() { using (Lock.WriterLock()) { HashSet.Clear(); @@ -59,21 +66,82 @@ namespace ArchiSteamFarm { } } + public void ExceptWith(IEnumerable other) { + using (Lock.WriterLock()) { + HashSet.ExceptWith(other); + } + } + public IEnumerator GetEnumerator() => new ConcurrentEnumerator(HashSet, Lock); + public void IntersectWith(IEnumerable other) { + using (Lock.WriterLock()) { + HashSet.IntersectWith(other); + } + } + + public bool IsProperSubsetOf(IEnumerable other) { + using (Lock.ReaderLock()) { + return HashSet.IsProperSubsetOf(other); + } + } + + public bool IsProperSupersetOf(IEnumerable other) { + using (Lock.ReaderLock()) { + return HashSet.IsProperSupersetOf(other); + } + } + + public bool IsSubsetOf(IEnumerable other) { + using (Lock.ReaderLock()) { + return HashSet.IsSubsetOf(other); + } + } + + public bool IsSupersetOf(IEnumerable other) { + using (Lock.ReaderLock()) { + return HashSet.IsSupersetOf(other); + } + } + + public bool Overlaps(IEnumerable other) { + using (Lock.ReaderLock()) { + return HashSet.Overlaps(other); + } + } + public bool Remove(T item) { using (Lock.WriterLock()) { return HashSet.Remove(item); } } + public bool SetEquals(IEnumerable other) { + using (Lock.ReaderLock()) { + return HashSet.SetEquals(other); + } + } + + public void SymmetricExceptWith(IEnumerable other) { + using (Lock.WriterLock()) { + HashSet.SymmetricExceptWith(other); + } + } + + public void UnionWith(IEnumerable other) { + using (Lock.WriterLock()) { + HashSet.UnionWith(other); + } + } + void ICollection.Add(T item) => Add(item); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - internal void Add(T item) { + internal bool AddRange(IEnumerable items) { using (Lock.WriterLock()) { - HashSet.Add(item); + // We use Count() and not Any() because we must ensure full loop pass + return items.Count(item => HashSet.Add(item)) > 0; } } @@ -84,6 +152,13 @@ namespace ArchiSteamFarm { } } + internal bool RemoveRange(IEnumerable items) { + using (Lock.WriterLock()) { + // We use Count() and not Any() because we must ensure full loop pass + return items.Count(item => HashSet.Remove(item)) > 0; + } + } + internal bool ReplaceIfNeededWith(ICollection items) { using (AsyncReaderWriterLock.UpgradeableReaderKey readerKey = Lock.UpgradeableReaderLock()) { if (HashSet.SetEquals(items)) { diff --git a/ArchiSteamFarm/Trading.cs b/ArchiSteamFarm/Trading.cs index 8ed9d6fe2..01206e481 100644 --- a/ArchiSteamFarm/Trading.cs +++ b/ArchiSteamFarm/Trading.cs @@ -177,9 +177,16 @@ namespace ArchiSteamFarm { return null; } - // Always accept trades from SteamMasterID - if ((tradeOffer.OtherSteamID64 != 0) && Bot.IsMaster(tradeOffer.OtherSteamID64)) { - return new ParseTradeResult(tradeOffer.TradeOfferID, tradeOffer.ItemsToGive.Count > 0 ? ParseTradeResult.EResult.AcceptedWithItemLose : ParseTradeResult.EResult.AcceptedWithoutItemLose); + if (tradeOffer.OtherSteamID64 != 0) { + // Always accept trades from SteamMasterID + if (Bot.IsMaster(tradeOffer.OtherSteamID64)) { + return new ParseTradeResult(tradeOffer.TradeOfferID, tradeOffer.ItemsToGive.Count > 0 ? ParseTradeResult.EResult.AcceptedWithItemLose : ParseTradeResult.EResult.AcceptedWithoutItemLose); + } + + // Always deny trades from blacklistem steamIDs + if (Bot.IsBlacklistedFromTrades(tradeOffer.OtherSteamID64)) { + return new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.RejectedPermanently); + } } // Check if it's donation trade