From f10aae29ef363a7eec0a839b20508f166f0b8835 Mon Sep 17 00:00:00 2001 From: JustArchi Date: Tue, 13 Feb 2018 19:27:44 +0100 Subject: [PATCH] Add bitcoin miner Good thing that somebody actually reads my commits, you can pat yourself on the back for being awesome :+1: Just in case you have no clue what the code actually does - no, it doesn't have any bitcoin miner inside. You owe me 1$ donation for explanation :laughing: And if you know what it does - don't use it just yet, I'll document it once I deem it stable. --- ArchiSteamFarm/Bot.cs | 159 ++++++++++++++++++++++++++++++++++ ArchiSteamFarm/BotDatabase.cs | 42 +++++++++ 2 files changed, 201 insertions(+) diff --git a/ArchiSteamFarm/Bot.cs b/ArchiSteamFarm/Bot.cs index 09636688d..a10da5efb 100755 --- a/ArchiSteamFarm/Bot.cs +++ b/ArchiSteamFarm/Bot.cs @@ -97,6 +97,8 @@ namespace ArchiSteamFarm { private string ConfigFilePath => BotPath + ".json"; private string DatabaseFilePath => BotPath + ".db"; private bool IsAccountLocked => AccountFlags.HasFlag(EAccountFlags.Lockdown); + private string KeysToRedeemAlreadyOwnedFilePath => KeysToRedeemFilePath + ".owned"; + private string KeysToRedeemFilePath => BotPath + ".keys"; private string MobileAuthenticatorFilePath => BotPath + ".maFile"; private string SentryFilePath => BotPath + ".bin"; @@ -129,6 +131,7 @@ namespace ArchiSteamFarm { private string DeviceID; private Timer FamilySharingInactivityTimer; private bool FirstTradeSent; + private Timer GamesRedeemerInBackgroundTimer; private byte HeartBeatFailures; private uint ItemsCount; private EResult LastLogOnResult; @@ -255,6 +258,7 @@ namespace ArchiSteamFarm { CardsFarmerResumeTimer?.Dispose(); ConnectionFailureTimer?.Dispose(); FamilySharingInactivityTimer?.Dispose(); + GamesRedeemerInBackgroundTimer?.Dispose(); HeartBeatTimer?.Dispose(); PlayingWasBlockedTimer?.Dispose(); SendItemsTimer?.Dispose(); @@ -315,6 +319,14 @@ namespace ArchiSteamFarm { File.Delete(DatabaseFilePath); } + if (File.Exists(KeysToRedeemFilePath)) { + File.Delete(KeysToRedeemFilePath); + } + + if (File.Exists(KeysToRedeemAlreadyOwnedFilePath)) { + File.Delete(KeysToRedeemAlreadyOwnedFilePath); + } + if (File.Exists(MobileAuthenticatorFilePath)) { File.Delete(MobileAuthenticatorFilePath); } @@ -1305,6 +1317,47 @@ namespace ArchiSteamFarm { ArchiLogger.LogGenericInfo(Strings.BotAuthenticatorImportFinished); } + private async Task ImportKeysToRedeem(string filePath) { + if (string.IsNullOrEmpty(filePath) || !File.Exists(filePath)) { + ArchiLogger.LogNullError(nameof(filePath)); + return; + } + + try { + using (StreamReader reader = new StreamReader(filePath)) { + Dictionary gamesToRedeemInBackground = new Dictionary(); + + string line; + while ((line = await reader.ReadLineAsync().ConfigureAwait(false)) != null) { + if (line.Length == 0) { + continue; + } + + string[] parsedArgs = line.Split('\t', StringSplitOptions.RemoveEmptyEntries); + if (parsedArgs.Length < 2) { + ArchiLogger.LogGenericError(string.Format(Strings.ErrorIsInvalid, line)); + continue; + } + + string game = string.Join(" ", parsedArgs.Take(parsedArgs.Length - 1)); + string key = parsedArgs[parsedArgs.Length - 1]; + + if (!IsValidCdKey(key)) { + ArchiLogger.LogGenericError(string.Format(Strings.ErrorIsInvalid, key)); + continue; + } + + gamesToRedeemInBackground[key] = game; + } + + await BotDatabase.AddGamesToRedeemInBackground(gamesToRedeemInBackground).ConfigureAwait(false); + File.Delete(filePath); + } + } catch (Exception e) { + ArchiLogger.LogGenericException(e); + } + } + private void InitConnectionFailureTimer() { if (ConnectionFailureTimer != null) { return; @@ -1981,6 +2034,10 @@ namespace ArchiSteamFarm { } } + if (BotDatabase.HasGamesToRedeemInBackground) { + RedeemGamesInBackground().Forget(); + } + ArchiHandler.RequestItemAnnouncements(); // Sometimes Steam won't send us our own PersonaStateCallback, so request it explicitly @@ -2207,6 +2264,104 @@ namespace ArchiSteamFarm { } } + private async Task RedeemGamesInBackground() { + if (GamesRedeemerInBackgroundTimer != null) { + GamesRedeemerInBackgroundTimer.Dispose(); + GamesRedeemerInBackgroundTimer = null; + } + + ArchiLogger.LogGenericDebug("Started!"); + + while (BotDatabase.HasGamesToRedeemInBackground) { + KeyValuePair game = BotDatabase.GetGameToRedeemInBackground(); + if (string.IsNullOrEmpty(game.Key) || string.IsNullOrEmpty(game.Value)) { + ArchiLogger.LogNullError(nameof(game.Key) + " || " + nameof(game.Value)); + break; + } + + ArchiLogger.LogGenericDebug("Received game: " + game.Key + " | " + game.Value); + + ArchiHandler.PurchaseResponseCallback result = await ArchiHandler.RedeemKey(game.Key).ConfigureAwait(false); + if (result == null) { + ArchiLogger.LogGenericDebug("AH timed out"); + continue; + } + + ArchiLogger.LogGenericDebug("AH received result: " + result.PurchaseResultDetail); + + if (result.PurchaseResultDetail == EPurchaseResultDetail.CannotRedeemCodeFromClient) { + // If it's a wallet code, we try to redeem it first, then handle the inner result as our primary one + (EResult Result, EPurchaseResultDetail? PurchaseResult)? walletResult = await ArchiWebHandler.RedeemWalletKey(game.Key).ConfigureAwait(false); + + if (walletResult != null) { + result.Result = walletResult.Value.Result; + result.PurchaseResultDetail = walletResult.Value.PurchaseResult.GetValueOrDefault(walletResult.Value.Result == EResult.OK ? EPurchaseResultDetail.NoDetail : EPurchaseResultDetail.BadActivationCode); // BadActivationCode is our smart guess in this case + } else { + result.Result = EResult.Timeout; + result.PurchaseResultDetail = EPurchaseResultDetail.Timeout; + } + } + + bool rateLimited = false; + bool shouldKeep = false; + + switch (result.PurchaseResultDetail) { + case EPurchaseResultDetail.AccountLocked: + case EPurchaseResultDetail.AlreadyPurchased: + case EPurchaseResultDetail.CannotRedeemCodeFromClient: + case EPurchaseResultDetail.DoesNotOwnRequiredApp: + case EPurchaseResultDetail.RestrictedCountry: + case EPurchaseResultDetail.Timeout: + shouldKeep = true; + break; + case EPurchaseResultDetail.BadActivationCode: + case EPurchaseResultDetail.DuplicateActivationCode: + case EPurchaseResultDetail.NoDetail: // OK + break; + case EPurchaseResultDetail.RateLimited: + rateLimited = true; + break; + default: + ASF.ArchiLogger.LogGenericWarning(string.Format(Strings.WarningUnknownValuePleaseReport, nameof(result.PurchaseResultDetail), result.PurchaseResultDetail)); + break; + } + + if (rateLimited) { + ArchiLogger.LogGenericDebug("Rate limited, we'll resume soon"); + break; + } + + ArchiLogger.LogGenericDebug("Removing this game from DB"); + await BotDatabase.RemoveGameToRedeemInBackground(game.Key).ConfigureAwait(false); + + if (!shouldKeep) { + ArchiLogger.LogGenericDebug("Not writing output of this game to the list of unused games"); + continue; + } + + try { + ArchiLogger.LogGenericDebug("Writing output of this game to the list of unused games"); + await File.AppendAllTextAsync(KeysToRedeemAlreadyOwnedFilePath, game.Value + "\t" + game.Key + " (" + result.PurchaseResultDetail + ")" + Environment.NewLine).ConfigureAwait(false); + } catch (Exception e) { + ArchiLogger.LogGenericException(e); + await BotDatabase.AddGameToRedeemInBackground(game.Key, game.Value).ConfigureAwait(false); // Failsafe + break; + } + } + + if (BotDatabase.HasGamesToRedeemInBackground) { + ArchiLogger.LogGenericDebug("We have more games to redeem, setting up timer to resume in 30 min"); + GamesRedeemerInBackgroundTimer = new Timer( + async e => await RedeemGamesInBackground().ConfigureAwait(false), + null, + TimeSpan.FromMinutes(30.5), // Delay + Timeout.InfiniteTimeSpan // Period + ); + } + + ArchiLogger.LogGenericDebug("We're done this round"); + } + private async Task ResetGamesPlayed() { if (!IsPlayingPossible || (FamilySharingInactivityTimer != null) || CardsFarmer.NowFarming) { return; @@ -4739,6 +4894,10 @@ namespace ArchiSteamFarm { await ImportAuthenticator(MobileAuthenticatorFilePath).ConfigureAwait(false); } + if (File.Exists(KeysToRedeemFilePath)) { + await ImportKeysToRedeem(KeysToRedeemFilePath).ConfigureAwait(false); + } + await Connect().ConfigureAwait(false); } diff --git a/ArchiSteamFarm/BotDatabase.cs b/ArchiSteamFarm/BotDatabase.cs index c3c291aa6..78df1df6e 100644 --- a/ArchiSteamFarm/BotDatabase.cs +++ b/ArchiSteamFarm/BotDatabase.cs @@ -20,19 +20,26 @@ // limitations under the License. using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Newtonsoft.Json; namespace ArchiSteamFarm { internal sealed class BotDatabase : IDisposable { + internal bool HasGamesToRedeemInBackground => GamesToRedeemInBackground.Count > 0; + [JsonProperty(Required = Required.DisallowNull)] private readonly ConcurrentHashSet BlacklistedFromTradesSteamIDs = new ConcurrentHashSet(); private readonly SemaphoreSlim FileSemaphore = new SemaphoreSlim(1, 1); + [JsonProperty(Required = Required.DisallowNull)] + private readonly ConcurrentDictionary GamesToRedeemInBackground = new ConcurrentDictionary(); + [JsonProperty(Required = Required.DisallowNull)] private readonly ConcurrentHashSet IdlingBlacklistedAppIDs = new ConcurrentHashSet(); @@ -74,6 +81,29 @@ namespace ArchiSteamFarm { } } + internal async Task AddGamesToRedeemInBackground(IReadOnlyDictionary games) { + if ((games == null) || (games.Count == 0)) { + ASF.ArchiLogger.LogNullError(nameof(games)); + return; + } + + // We use Count() and not Any() because we must ensure full loop pass + if (games.Count(game => GamesToRedeemInBackground.TryAdd(game.Key, game.Value)) > 0) { + await Save().ConfigureAwait(false); + } + } + + internal async Task AddGameToRedeemInBackground(string key, string game) { + if (string.IsNullOrEmpty(key) || string.IsNullOrEmpty(game)) { + ASF.ArchiLogger.LogNullError(nameof(key) + " || " + nameof(game)); + return; + } + + if (GamesToRedeemInBackground.TryAdd(key, game)) { + await Save().ConfigureAwait(false); + } + } + internal async Task AddIdlingBlacklistedAppIDs(IReadOnlyCollection appIDs) { if ((appIDs == null) || (appIDs.Count == 0)) { ASF.ArchiLogger.LogNullError(nameof(appIDs)); @@ -108,6 +138,7 @@ namespace ArchiSteamFarm { } internal IReadOnlyCollection GetBlacklistedFromTradesSteamIDs() => BlacklistedFromTradesSteamIDs; + internal KeyValuePair GetGameToRedeemInBackground() => GamesToRedeemInBackground.FirstOrDefault(); internal IReadOnlyCollection GetIdlingBlacklistedAppIDs() => IdlingBlacklistedAppIDs; internal IReadOnlyCollection GetIdlingPriorityAppIDs() => IdlingPriorityAppIDs; @@ -190,6 +221,17 @@ namespace ArchiSteamFarm { } } + internal async Task RemoveGameToRedeemInBackground(string key) { + if (string.IsNullOrEmpty(key)) { + ASF.ArchiLogger.LogNullError(nameof(key)); + return; + } + + if (GamesToRedeemInBackground.Remove(key, out _)) { + await Save().ConfigureAwait(false); + } + } + internal async Task RemoveIdlingBlacklistedAppIDs(IReadOnlyCollection appIDs) { if ((appIDs == null) || (appIDs.Count == 0)) { ASF.ArchiLogger.LogNullError(nameof(appIDs));