Example #1
0
        internal Bot(string botName)
        {
            if (Bots.ContainsKey(botName))
            {
                return;
            }

            BotName = botName;

            ConfigFile = Path.Combine(Program.ConfigDirectoryPath, BotName + ".xml");
            SentryFile = Path.Combine(Program.ConfigDirectoryPath, BotName + ".bin");

            if (!ReadConfig())
            {
                return;
            }

            if (!Enabled)
            {
                return;
            }

            lock (Bots) {
                Bots.Add(BotName, this);
            }

            // Initialize
            SteamClient = new SteamClient();

            ArchiHandler = new ArchiHandler();
            SteamClient.AddHandler(ArchiHandler);

            CallbackManager = new CallbackManager(SteamClient);
            CallbackManager.Subscribe <SteamClient.ConnectedCallback>(OnConnected);
            CallbackManager.Subscribe <SteamClient.DisconnectedCallback>(OnDisconnected);

            SteamFriends = SteamClient.GetHandler <SteamFriends>();
            CallbackManager.Subscribe <SteamFriends.FriendsListCallback>(OnFriendsList);
            CallbackManager.Subscribe <SteamFriends.FriendMsgCallback>(OnFriendMsg);
            CallbackManager.Subscribe <SteamFriends.PersonaStateCallback>(OnPersonaState);

            SteamUser = SteamClient.GetHandler <SteamUser>();
            CallbackManager.Subscribe <SteamUser.AccountInfoCallback>(OnAccountInfo);
            CallbackManager.Subscribe <SteamUser.LoggedOffCallback>(OnLoggedOff);
            CallbackManager.Subscribe <SteamUser.LoggedOnCallback>(OnLoggedOn);
            CallbackManager.Subscribe <SteamUser.UpdateMachineAuthCallback>(OnMachineAuth);

            CallbackManager.Subscribe <ArchiHandler.NotificationCallback>(OnNotification);
            CallbackManager.Subscribe <ArchiHandler.PurchaseResponseCallback>(OnPurchaseResponse);

            ArchiWebHandler = new ArchiWebHandler(this, SteamApiKey);
            CardsFarmer     = new CardsFarmer(this);
            Trading         = new Trading(this);

            // Start
            Start();
        }
Example #2
0
        private void OnNotification(ArchiHandler.NotificationCallback callback)
        {
            if (callback == null)
            {
                return;
            }

            switch (callback.NotificationType)
            {
            case ArchiHandler.NotificationCallback.ENotificationType.Trading:
                Trading.CheckTrades();
                break;
            }
        }
Example #3
0
        internal void Start()
        {
            if (SteamClient != null)
            {
                return;
            }

            SteamClient = new SteamClient();

            ArchiHandler = new ArchiHandler();
            SteamClient.AddHandler(ArchiHandler);

            CallbackManager = new CallbackManager(SteamClient);
            CallbackManager.Subscribe <SteamClient.ConnectedCallback>(OnConnected);
            CallbackManager.Subscribe <SteamClient.DisconnectedCallback>(OnDisconnected);

            SteamFriends = SteamClient.GetHandler <SteamFriends>();
            CallbackManager.Subscribe <SteamFriends.FriendsListCallback>(OnFriendsList);
            CallbackManager.Subscribe <SteamFriends.FriendMsgCallback>(OnFriendMsg);
            CallbackManager.Subscribe <SteamFriends.PersonaStateCallback>(OnPersonaState);

            SteamUser = SteamClient.GetHandler <SteamUser>();
            CallbackManager.Subscribe <SteamUser.AccountInfoCallback>(OnAccountInfo);
            CallbackManager.Subscribe <SteamUser.LoggedOffCallback>(OnLoggedOff);
            CallbackManager.Subscribe <SteamUser.LoggedOnCallback>(OnLoggedOn);
            CallbackManager.Subscribe <SteamUser.UpdateMachineAuthCallback>(OnMachineAuth);

            CallbackManager.Subscribe <ArchiHandler.NotificationCallback>(OnNotification);
            CallbackManager.Subscribe <ArchiHandler.PurchaseResponseCallback>(OnPurchaseResponse);

            ArchiWebHandler = new ArchiWebHandler(this, SteamApiKey);
            Trading         = new Trading(this);

            SteamClient.Connect();
            Task.Run(() => HandleCallbacks());
        }
Example #4
0
        private async Task <bool> MatchActivelyRound()
        {
            // TODO: This function has a lot of debug leftovers for logic testing, once that period is over, get rid of them
            if (!Bot.IsConnectedAndLoggedOn || Bot.BotConfig.TradingPreferences.HasFlag(BotConfig.ETradingPreferences.MatchEverything) || !Bot.BotConfig.TradingPreferences.HasFlag(BotConfig.ETradingPreferences.MatchActively) || !await IsEligibleForMatching().ConfigureAwait(false))
            {
                Bot.ArchiLogger.LogGenericDebug("User not eligible for this function, returning");
                return(false);
            }

            HashSet <Steam.Asset.EType> acceptedMatchableTypes = Bot.BotConfig.MatchableTypes.Where(type => AcceptedMatchableTypes.Contains(type)).ToHashSet();

            if (acceptedMatchableTypes.Count == 0)
            {
                Bot.ArchiLogger.LogGenericDebug("No acceptable matchable types, returning");
                return(false);
            }

            HashSet <Steam.Asset> ourInventory = await Bot.ArchiWebHandler.GetInventory(Bot.SteamID, tradable : true, wantedTypes : acceptedMatchableTypes).ConfigureAwait(false);

            if ((ourInventory == null) || (ourInventory.Count == 0))
            {
                Bot.ArchiLogger.LogGenericDebug("Empty inventory, returning");
                return(false);
            }

            Dictionary <(uint AppID, Steam.Asset.EType Type), Dictionary <ulong, uint> > ourInventoryState = Trading.GetInventoryState(ourInventory);

            if (ourInventoryState.Values.All(set => set.Values.All(amount => amount <= 1)))
            {
                // User doesn't have any more dupes in the inventory
                Bot.ArchiLogger.LogGenericDebug("No dupes in inventory, returning");
                return(false);
            }

            HashSet <ListedUser> listedUsers = await GetListedUsers().ConfigureAwait(false);

            if ((listedUsers == null) || (listedUsers.Count == 0))
            {
                Bot.ArchiLogger.LogGenericDebug("No listed users, returning");
                return(false);
            }

            byte emptyMatches = 0;
            HashSet <(uint AppID, Steam.Asset.EType Type)> skippedSets = new HashSet <(uint AppID, Steam.Asset.EType Type)>();

            foreach (ListedUser listedUser in listedUsers.Where(listedUser => listedUser.MatchEverything && !Bot.IsBlacklistedFromTrades(listedUser.SteamID)).OrderByDescending(listedUser => listedUser.Score).Take(MaxMatchedBotsHard))
            {
                Bot.ArchiLogger.LogGenericDebug("Now matching " + listedUser.SteamID + "...");

                HashSet <Steam.Asset> theirInventory = await Bot.ArchiWebHandler.GetInventory(listedUser.SteamID, tradable : true, wantedTypes : acceptedMatchableTypes, skippedSets : skippedSets).ConfigureAwait(false);

                if ((theirInventory == null) || (theirInventory.Count == 0))
                {
                    Bot.ArchiLogger.LogGenericDebug("Inventory of " + listedUser.SteamID + " is empty, continuing...");
                    continue;
                }

                Dictionary <(uint AppID, Steam.Asset.EType Type), Dictionary <ulong, uint> > theirInventoryState = Trading.GetInventoryState(theirInventory);

                Dictionary <ulong, uint> classIDsToGive    = new Dictionary <ulong, uint>();
                Dictionary <ulong, uint> classIDsToReceive = new Dictionary <ulong, uint>();
                HashSet <(uint AppID, Steam.Asset.EType Type)> skippedSetsThisTrade = new HashSet <(uint AppID, Steam.Asset.EType Type)>();

                foreach (KeyValuePair <(uint AppID, Steam.Asset.EType Type), Dictionary <ulong, uint> > ourInventoryStateSet in ourInventoryState.Where(set => listedUser.MatchableTypes.Contains(set.Key.Type) && set.Value.Values.Any(count => count > 1)))
                {
                    if (!theirInventoryState.TryGetValue(ourInventoryStateSet.Key, out Dictionary <ulong, uint> theirItems))
                    {
                        continue;
                    }

                    bool match;

                    do
                    {
                        match = false;

                        foreach (KeyValuePair <ulong, uint> ourItem in ourInventoryStateSet.Value.Where(item => item.Value > 1).OrderByDescending(item => item.Value))
                        {
                            foreach (KeyValuePair <ulong, uint> theirItem in theirItems.OrderBy(item => ourInventoryStateSet.Value.TryGetValue(item.Key, out uint ourAmount) ? ourAmount : 0))
                            {
                                if (ourInventoryStateSet.Value.TryGetValue(theirItem.Key, out uint ourAmountOfTheirItem) && (ourItem.Value <= ourAmountOfTheirItem + 1))
                                {
                                    continue;
                                }

                                Bot.ArchiLogger.LogGenericDebug("Found a match: our " + ourItem.Key + " for theirs " + theirItem.Key);

                                // Skip this set from the remaining of this round
                                skippedSetsThisTrade.Add(ourInventoryStateSet.Key);

                                // Update our state based on given items
                                classIDsToGive[ourItem.Key]             = classIDsToGive.TryGetValue(ourItem.Key, out uint givenAmount) ? givenAmount + 1 : 1;
                                ourInventoryStateSet.Value[ourItem.Key] = ourItem.Value - 1;

                                // Update our state based on received items
                                classIDsToReceive[theirItem.Key]          = classIDsToReceive.TryGetValue(theirItem.Key, out uint receivedAmount) ? receivedAmount + 1 : 1;
                                ourInventoryStateSet.Value[theirItem.Key] = ourAmountOfTheirItem + 1;

                                // Update their state based on taken items
                                if (theirItems.TryGetValue(theirItem.Key, out uint theirAmount) && (theirAmount > 1))
                                {
                                    theirItems[theirItem.Key] = theirAmount - 1;
                                }
                                else
                                {
                                    theirItems.Remove(theirItem.Key);
                                }

                                match = true;
                                break;
                            }

                            if (match)
                            {
                                break;
                            }
                        }
                    } while (match);
                }

                if ((classIDsToGive.Count == 0) && (classIDsToReceive.Count == 0))
                {
                    Bot.ArchiLogger.LogGenericDebug("No matches found, continuing...");

                    if (++emptyMatches >= MaxMatchesBotsSoft)
                    {
                        break;
                    }

                    continue;
                }

                emptyMatches = 0;

                HashSet <Steam.Asset> itemsToGive    = Trading.GetItemsFromInventory(ourInventory, classIDsToGive);
                HashSet <Steam.Asset> itemsToReceive = Trading.GetItemsFromInventory(theirInventory, classIDsToReceive);

                // TODO: Debug only offer, should be removed after tests
                Steam.TradeOffer debugOffer = new Steam.TradeOffer(1, 46697991, Steam.TradeOffer.ETradeOfferState.Active);

                foreach (Steam.Asset itemToGive in itemsToGive)
                {
                    debugOffer.ItemsToGive.Add(itemToGive);
                }

                foreach (Steam.Asset itemToReceive in itemsToReceive)
                {
                    debugOffer.ItemsToReceive.Add(itemToReceive);
                }

                if (!debugOffer.IsFairTypesExchange())
                {
                    Bot.ArchiLogger.LogGenericDebug("CRITICAL: This offer is NOT fair!!!");
                    return(false);
                }

                Bot.ArchiLogger.LogGenericDebug("Sending trade: our " + string.Join(", ", itemsToGive.Select(item => item.RealAppID + "/" + item.Type + " " + item.ClassID + " of " + item.Amount)));

                (bool success, HashSet <ulong> mobileTradeOfferIDs) = await Bot.ArchiWebHandler.SendTradeOffer(listedUser.SteamID, itemsToGive, itemsToReceive, listedUser.TradeToken, true).ConfigureAwait(false);

                if ((mobileTradeOfferIDs != null) && (mobileTradeOfferIDs.Count > 0) && Bot.HasMobileAuthenticator)
                {
                    if (!await Bot.Actions.AcceptConfirmations(true, Steam.ConfirmationDetails.EType.Trade, listedUser.SteamID, mobileTradeOfferIDs, true).ConfigureAwait(false))
                    {
                        return(false);
                    }
                }

                if (!success)
                {
                    Bot.ArchiLogger.LogGenericDebug("Trade failed (?), continuing...");
                    continue;
                }

                Bot.ArchiLogger.LogGenericDebug("Trade succeeded!");

                foreach ((uint AppID, Steam.Asset.EType Type)skippedSetThisTrade in skippedSetsThisTrade)
                {
                    ourInventoryState.Remove(skippedSetThisTrade);
                }

                if (ourInventoryState.Values.All(set => set.Values.All(amount => amount <= 1)))
                {
                    // User doesn't have any more dupes in the inventory
                    Bot.ArchiLogger.LogGenericDebug("No dupes in inventory, returning");
                    break;
                }

                skippedSets.UnionWith(skippedSetsThisTrade);
            }

            Bot.ArchiLogger.LogGenericDebug("This round is over, we traded " + skippedSets.Count + " sets!");
            return(skippedSets.Count > 0);
        }
Example #5
0
		internal Bot(string botName) {
			if (string.IsNullOrEmpty(botName)) {
				throw new ArgumentNullException(nameof(botName));
			}

			if (Bots.ContainsKey(botName)) {
				throw new ArgumentException("That bot is already defined!");
			}

			string botPath = Path.Combine(SharedInfo.ConfigDirectory, botName);

			BotName = botName;
			SentryFile = botPath + ".bin";

			string botConfigFile = botPath + ".json";

			BotConfig = BotConfig.Load(botConfigFile);
			if (BotConfig == null) {
				Logging.LogGenericError("Your bot config is invalid, please verify content of " + botConfigFile + " and try again!", botName);
				return;
			}

			// Register bot as available for ASF
			if (!Bots.TryAdd(botName, this)) {
				throw new ArgumentException("That bot is already defined!");
			}

			string botDatabaseFile = botPath + ".db";

			BotDatabase = BotDatabase.Load(botDatabaseFile);
			if (BotDatabase == null) {
				Logging.LogGenericError("Bot database could not be loaded, refusing to create this bot instance! In order to recreate it, remove " + botDatabaseFile + " and try again!", botName);
				return;
			}

			if (BotDatabase.MobileAuthenticator != null) {
				BotDatabase.MobileAuthenticator.Init(this);
			} else {
				// Support and convert SDA files
				string maFilePath = botPath + ".maFile";
				if (File.Exists(maFilePath)) {
					ImportAuthenticator(maFilePath);
				}
			}

			// Initialize
			SteamClient = new SteamClient(Program.GlobalConfig.SteamProtocol);

			if (Program.GlobalConfig.Debug && Directory.Exists(SharedInfo.DebugDirectory)) {
				string debugListenerPath = Path.Combine(SharedInfo.DebugDirectory, botName);

				try {
					Directory.CreateDirectory(debugListenerPath);
					SteamClient.DebugNetworkListener = new NetHookNetworkListener(debugListenerPath);
				} catch (Exception e) {
					Logging.LogGenericException(e, botName);
				}
			}

			ArchiHandler = new ArchiHandler(this);
			SteamClient.AddHandler(ArchiHandler);

			CallbackManager = new CallbackManager(SteamClient);
			CallbackManager.Subscribe<SteamClient.ConnectedCallback>(OnConnected);
			CallbackManager.Subscribe<SteamClient.DisconnectedCallback>(OnDisconnected);

			SteamApps = SteamClient.GetHandler<SteamApps>();
			CallbackManager.Subscribe<SteamApps.FreeLicenseCallback>(OnFreeLicense);
			CallbackManager.Subscribe<SteamApps.GuestPassListCallback>(OnGuestPassList);
			CallbackManager.Subscribe<SteamApps.LicenseListCallback>(OnLicenseList);

			SteamFriends = SteamClient.GetHandler<SteamFriends>();
			CallbackManager.Subscribe<SteamFriends.ChatInviteCallback>(OnChatInvite);
			CallbackManager.Subscribe<SteamFriends.ChatMsgCallback>(OnChatMsg);
			CallbackManager.Subscribe<SteamFriends.FriendsListCallback>(OnFriendsList);
			CallbackManager.Subscribe<SteamFriends.FriendMsgCallback>(OnFriendMsg);
			CallbackManager.Subscribe<SteamFriends.FriendMsgHistoryCallback>(OnFriendMsgHistory);
			CallbackManager.Subscribe<SteamFriends.PersonaStateCallback>(OnPersonaState);

			SteamUser = SteamClient.GetHandler<SteamUser>();
			CallbackManager.Subscribe<SteamUser.AccountInfoCallback>(OnAccountInfo);
			CallbackManager.Subscribe<SteamUser.LoggedOffCallback>(OnLoggedOff);
			CallbackManager.Subscribe<SteamUser.LoggedOnCallback>(OnLoggedOn);
			CallbackManager.Subscribe<SteamUser.LoginKeyCallback>(OnLoginKey);
			CallbackManager.Subscribe<SteamUser.UpdateMachineAuthCallback>(OnMachineAuth);
			CallbackManager.Subscribe<SteamUser.WebAPIUserNonceCallback>(OnWebAPIUserNonce);

			CallbackManager.Subscribe<ArchiHandler.NotificationsCallback>(OnNotifications);
			CallbackManager.Subscribe<ArchiHandler.OfflineMessageCallback>(OnOfflineMessage);
			CallbackManager.Subscribe<ArchiHandler.PlayingSessionStateCallback>(OnPlayingSessionState);
			CallbackManager.Subscribe<ArchiHandler.PurchaseResponseCallback>(OnPurchaseResponse);
			CallbackManager.Subscribe<ArchiHandler.SharedLibraryLockStatusCallback>(OnSharedLibraryLockStatus);

			ArchiWebHandler = new ArchiWebHandler(this);

			CardsFarmer = new CardsFarmer(this) {
				Paused = BotConfig.Paused
			};

			Trading = new Trading(this);

			HeartBeatTimer = new Timer(
				async e => await HeartBeat().ConfigureAwait(false),
				null,
				TimeSpan.FromMinutes(1) + TimeSpan.FromMinutes(0.2 * Bots.Count), // Delay
				TimeSpan.FromMinutes(1) // Period
			);

			Initialize().Forget();
		}
Example #6
0
        internal async Task OnPersonaState(SteamFriends.PersonaStateCallback callback)
        {
            if (callback == null)
            {
                ASF.ArchiLogger.LogNullError(nameof(callback));
                return;
            }

            if (DateTime.UtcNow < LastAnnouncementCheck.AddHours(MinAnnouncementCheckTTL))
            {
                return;
            }

            // Don't announce if we don't meet conditions
            if (!Bot.HasMobileAuthenticator || !Bot.BotConfig.TradingPreferences.HasFlag(BotConfig.ETradingPreferences.SteamTradeMatcher) || !await Bot.ArchiWebHandler.HasValidApiKey().ConfigureAwait(false) || !await Bot.ArchiWebHandler.HasPublicInventory().ConfigureAwait(false))
            {
                LastAnnouncementCheck = DateTime.UtcNow;
                ShouldSendHeartBeats  = false;
                return;
            }

            string nickname = callback.Name ?? "";

            string avatarHash = "";

            if ((callback.AvatarHash != null) && (callback.AvatarHash.Length > 0) && callback.AvatarHash.Any(singleByte => singleByte != 0))
            {
                avatarHash = BitConverter.ToString(callback.AvatarHash).Replace("-", "").ToLowerInvariant();
                if (avatarHash.All(singleChar => singleChar == '0'))
                {
                    avatarHash = "";
                }
            }

            bool matchEverything = Bot.BotConfig.TradingPreferences.HasFlag(BotConfig.ETradingPreferences.MatchEverything);

            await Semaphore.WaitAsync().ConfigureAwait(false);

            try {
                if (DateTime.UtcNow < LastAnnouncementCheck.AddHours(MinAnnouncementCheckTTL))
                {
                    return;
                }

                await Trading.LimitInventoryRequestsAsync().ConfigureAwait(false);

                HashSet <Steam.Item> inventory = await Bot.ArchiWebHandler.GetMySteamInventory(true, new HashSet <Steam.Item.EType> {
                    Steam.Item.EType.TradingCard
                }).ConfigureAwait(false);

                // This is actually inventory failure, so we'll stop sending heartbeats but not record it as valid check
                if (inventory == null)
                {
                    ShouldSendHeartBeats = false;
                    return;
                }

                // This is actual inventory
                if (inventory.Count < MinCardsCount)
                {
                    LastAnnouncementCheck = DateTime.UtcNow;
                    ShouldSendHeartBeats  = false;
                    return;
                }

                string request = await GetURL().ConfigureAwait(false) + "/api/Announce";

                Dictionary <string, string> data = new Dictionary <string, string>(6)
                {
                    { "SteamID", Bot.SteamID.ToString() },
                    { "Guid", Program.GlobalDatabase.Guid.ToString("N") },
                    { "Nickname", nickname },
                    { "AvatarHash", avatarHash },
                    { "MatchEverything", matchEverything ? "1" : "0" },
                    { "CardsCount", inventory.Count.ToString() }
                };

                // We don't need retry logic here
                if (await Program.WebBrowser.UrlPost(request, data).ConfigureAwait(false))
                {
                    LastAnnouncementCheck = DateTime.UtcNow;
                    ShouldSendHeartBeats  = true;
                }
            } finally {
                Semaphore.Release();
            }
        }
Example #7
0
		internal Bot(string botName) {
			if (Bots.ContainsKey(botName)) {
				return;
			}

			BotName = botName;

			ConfigFile = Path.Combine(Program.ConfigDirectoryPath, BotName + ".xml");
			SentryFile = Path.Combine(Program.ConfigDirectoryPath, BotName + ".bin");

			if (!ReadConfig()) {
				return;
			}

			if (!Enabled) {
				return;
			}

			lock (Bots) {
				Bots.Add(BotName, this);
			}

			// Initialize
			SteamClient = new SteamClient();

			ArchiHandler = new ArchiHandler();
			SteamClient.AddHandler(ArchiHandler);

			CallbackManager = new CallbackManager(SteamClient);
			CallbackManager.Subscribe<SteamClient.ConnectedCallback>(OnConnected);
			CallbackManager.Subscribe<SteamClient.DisconnectedCallback>(OnDisconnected);

			SteamFriends = SteamClient.GetHandler<SteamFriends>();
			CallbackManager.Subscribe<SteamFriends.FriendsListCallback>(OnFriendsList);
			CallbackManager.Subscribe<SteamFriends.FriendMsgCallback>(OnFriendMsg);
			CallbackManager.Subscribe<SteamFriends.PersonaStateCallback>(OnPersonaState);

			SteamUser = SteamClient.GetHandler<SteamUser>();
			CallbackManager.Subscribe<SteamUser.AccountInfoCallback>(OnAccountInfo);
			CallbackManager.Subscribe<SteamUser.LoggedOffCallback>(OnLoggedOff);
			CallbackManager.Subscribe<SteamUser.LoggedOnCallback>(OnLoggedOn);
			CallbackManager.Subscribe<SteamUser.UpdateMachineAuthCallback>(OnMachineAuth);

			CallbackManager.Subscribe<ArchiHandler.NotificationCallback>(OnNotification);
			CallbackManager.Subscribe<ArchiHandler.PurchaseResponseCallback>(OnPurchaseResponse);

			ArchiWebHandler = new ArchiWebHandler(this, SteamApiKey);
			CardsFarmer = new CardsFarmer(this);
			Trading = new Trading(this);

			// Start
			Start();
		}
Example #8
0
        internal async Task OnPersonaState(SteamFriends.PersonaStateCallback callback)
        {
            if (callback == null)
            {
                ASF.ArchiLogger.LogNullError(nameof(callback));
                return;
            }

            // Don't announce if we don't meet conditions
            if (!Bot.HasMobileAuthenticator || !Bot.BotConfig.TradingPreferences.HasFlag(BotConfig.ETradingPreferences.SteamTradeMatcher) || !await Bot.ArchiWebHandler.HasValidApiKey().ConfigureAwait(false) || !await Bot.ArchiWebHandler.HasPublicInventory().ConfigureAwait(false))
            {
                ShouldSendHeartBeats = false;
                return;
            }

            string nickname = callback.Name ?? "";

            string avatarHash = "";

            if ((callback.AvatarHash != null) && (callback.AvatarHash.Length > 0) && callback.AvatarHash.Any(singleByte => singleByte != 0))
            {
                avatarHash = BitConverter.ToString(callback.AvatarHash).Replace("-", "").ToLowerInvariant();
                if (avatarHash.All(singleChar => singleChar == '0'))
                {
                    avatarHash = "";
                }
            }

            bool matchEverything = Bot.BotConfig.TradingPreferences.HasFlag(BotConfig.ETradingPreferences.MatchEverything);

            // Skip announcing if we already announced this bot with the same data
            if (ShouldSendHeartBeats && (LastNickname != null) && nickname.Equals(LastNickname) && (LastAvatarHash != null) && avatarHash.Equals(LastAvatarHash) && LastMatchEverything.HasValue && (matchEverything == LastMatchEverything.Value))
            {
                return;
            }

            await Semaphore.WaitAsync().ConfigureAwait(false);

            try {
                // Skip announcing if we already announced this bot with the same data
                if (ShouldSendHeartBeats && (LastNickname != null) && nickname.Equals(LastNickname) && (LastAvatarHash != null) && avatarHash.Equals(LastAvatarHash) && LastMatchEverything.HasValue && (matchEverything == LastMatchEverything.Value))
                {
                    return;
                }

                await Trading.LimitInventoryRequestsAsync().ConfigureAwait(false);

                HashSet <Steam.Item> inventory = await Bot.ArchiWebHandler.GetMySteamInventory(true, new HashSet <Steam.Item.EType> {
                    Steam.Item.EType.TradingCard
                }).ConfigureAwait(false);

                if ((inventory == null) || (inventory.Count == 0))
                {
                    // Don't announce, we have empty inventory
                    ShouldSendHeartBeats = false;
                    return;
                }

                // Even if following request fails, we want to send HeartBeats regardless
                ShouldSendHeartBeats = true;

                string request = await GetURL().ConfigureAwait(false) + "/api/Announce";

                Dictionary <string, string> data = new Dictionary <string, string>(6)
                {
                    { "SteamID", Bot.SteamID.ToString() },
                    { "Guid", Program.GlobalDatabase.Guid.ToString("N") },
                    { "Nickname", nickname },
                    { "AvatarHash", avatarHash },
                    { "MatchEverything", matchEverything ? "1" : "0" },
                    { "CardsCount", inventory.Count.ToString() }
                };

                // We don't need retry logic here
                if (await Program.WebBrowser.UrlPost(request, data).ConfigureAwait(false))
                {
                    LastNickname        = nickname;
                    LastAvatarHash      = avatarHash;
                    LastMatchEverything = matchEverything;
                }
            } finally {
                Semaphore.Release();
            }
        }
Example #9
0
		internal Bot(string botName, bool initialLaunch = false) {
			if (Bots.ContainsKey(botName)) {
				return;
			}

			BotName = botName;

			ConfigFile = Path.Combine(Program.ConfigDirectory, BotName + ".xml");
			LoginKeyFile = Path.Combine(Program.ConfigDirectory, BotName + ".key");
			MobileAuthenticatorFile = Path.Combine(Program.ConfigDirectory, BotName + ".auth");
			SentryFile = Path.Combine(Program.ConfigDirectory, BotName + ".bin");

			if (!ReadConfig()) {
				return;
			}

			if (!Enabled) {
				return;
			}

			Bots.AddOrUpdate(BotName, this, (key, value) => this);

			// Initialize
			SteamClient = new SteamClient();

			ArchiHandler = new ArchiHandler();
			SteamClient.AddHandler(ArchiHandler);

			CallbackManager = new CallbackManager(SteamClient);
			CallbackManager.Subscribe<SteamClient.ConnectedCallback>(OnConnected);
			CallbackManager.Subscribe<SteamClient.DisconnectedCallback>(OnDisconnected);

			SteamApps = SteamClient.GetHandler<SteamApps>();
			CallbackManager.Subscribe<SteamApps.FreeLicenseCallback>(OnFreeLicense);

			SteamFriends = SteamClient.GetHandler<SteamFriends>();
			CallbackManager.Subscribe<SteamFriends.ChatInviteCallback>(OnChatInvite);
			CallbackManager.Subscribe<SteamFriends.ChatMsgCallback>(OnChatMsg);
			CallbackManager.Subscribe<SteamFriends.FriendsListCallback>(OnFriendsList);
			CallbackManager.Subscribe<SteamFriends.FriendMsgCallback>(OnFriendMsg);
			CallbackManager.Subscribe<SteamFriends.FriendMsgHistoryCallback>(OnFriendMsgHistory);

			if (UseAsfAsMobileAuthenticator && File.Exists(MobileAuthenticatorFile)) {
				SteamGuardAccount = JsonConvert.DeserializeObject<SteamGuardAccount>(File.ReadAllText(MobileAuthenticatorFile));
			}

			SteamUser = SteamClient.GetHandler<SteamUser>();
			CallbackManager.Subscribe<SteamUser.AccountInfoCallback>(OnAccountInfo);
			CallbackManager.Subscribe<SteamUser.LoggedOffCallback>(OnLoggedOff);
			CallbackManager.Subscribe<SteamUser.LoggedOnCallback>(OnLoggedOn);
			CallbackManager.Subscribe<SteamUser.LoginKeyCallback>(OnLoginKey);
			CallbackManager.Subscribe<SteamUser.UpdateMachineAuthCallback>(OnMachineAuth);

			CallbackManager.Subscribe<ArchiHandler.NotificationsCallback>(OnNotifications);
			CallbackManager.Subscribe<ArchiHandler.OfflineMessageCallback>(OnOfflineMessage);
			CallbackManager.Subscribe<ArchiHandler.PurchaseResponseCallback>(OnPurchaseResponse);

			ArchiWebHandler = new ArchiWebHandler(this, SteamApiKey);
			CardsFarmer = new CardsFarmer(this);
			Trading = new Trading(this);

			if (SendTradePeriod > 0 && SendItemsTimer == null) {
				SendItemsTimer = new Timer(
					async e => await ResponseSendTrade(BotName).ConfigureAwait(false),
					null,
					TimeSpan.FromHours(SendTradePeriod), // Delay
					TimeSpan.FromHours(SendTradePeriod) // Period
				);
			}

			if (initialLaunch && !StartOnLaunch) {
				return;
			}

			// Start
			var start = Task.Run(async () => await Start().ConfigureAwait(false));
		}
Example #10
0
		internal void Start() {
			if (SteamClient != null) {
				return;
			}

			SteamClient = new SteamClient();

			ArchiHandler = new ArchiHandler();
			SteamClient.AddHandler(ArchiHandler);

			CallbackManager = new CallbackManager(SteamClient);
			CallbackManager.Subscribe<SteamClient.ConnectedCallback>(OnConnected);
			CallbackManager.Subscribe<SteamClient.DisconnectedCallback>(OnDisconnected);

			SteamFriends = SteamClient.GetHandler<SteamFriends>();
			CallbackManager.Subscribe<SteamFriends.FriendsListCallback>(OnFriendsList);
			CallbackManager.Subscribe<SteamFriends.FriendMsgCallback>(OnFriendMsg);
			CallbackManager.Subscribe<SteamFriends.PersonaStateCallback>(OnPersonaState);

			SteamUser = SteamClient.GetHandler<SteamUser>();
			CallbackManager.Subscribe<SteamUser.AccountInfoCallback>(OnAccountInfo);
			CallbackManager.Subscribe<SteamUser.LoggedOffCallback>(OnLoggedOff);
			CallbackManager.Subscribe<SteamUser.LoggedOnCallback>(OnLoggedOn);
			CallbackManager.Subscribe<SteamUser.UpdateMachineAuthCallback>(OnMachineAuth);

			CallbackManager.Subscribe<ArchiHandler.NotificationCallback>(OnNotification);
			CallbackManager.Subscribe<ArchiHandler.PurchaseResponseCallback>(OnPurchaseResponse);

			ArchiWebHandler = new ArchiWebHandler(this, SteamApiKey);
			Trading = new Trading(this);

			SteamClient.Connect();
			Task.Run(() => HandleCallbacks());
		}
Example #11
0
		internal Bot(string botName) {
			if (string.IsNullOrEmpty(botName)) {
				throw new ArgumentNullException(nameof(botName));
			}

			if (Bots.ContainsKey(botName)) {
				throw new Exception("That bot is already defined!");
			}

			string botPath = Path.Combine(Program.ConfigDirectory, botName);

			BotName = botName;
			SentryFile = botPath + ".bin";

			BotConfig = BotConfig.Load(botPath + ".json");
			if (BotConfig == null) {
				Logging.LogGenericError("Your bot config is invalid, refusing to start this bot instance!", botName);
				return;
			}

			if (!BotConfig.Enabled) {
				Logging.LogGenericInfo("Not initializing this instance because it's disabled in config file", botName);
				return;
			}

			BotDatabase = BotDatabase.Load(botPath + ".db");
			if (BotDatabase == null) {
				Logging.LogGenericError("Bot database could not be loaded, refusing to start this bot instance!", botName);
				return;
			}

			// TODO: Converter code will be removed soon
			if (BotDatabase.SteamGuardAccount != null) {
				Logging.LogGenericWarning("Converting old ASF 2FA V2.0 format into new ASF 2FA V2.1 format...", botName);
				BotDatabase.MobileAuthenticator = MobileAuthenticator.LoadFromSteamGuardAccount(BotDatabase.SteamGuardAccount);
				Logging.LogGenericInfo("Done! If you didn't make a copy of your revocation code yet, then it's a good moment to do so: " + BotDatabase.SteamGuardAccount.RevocationCode, botName);
				Logging.LogGenericWarning("ASF will not keep this code anymore!", botName);
				BotDatabase.SteamGuardAccount = null;
			}

			if (BotDatabase.MobileAuthenticator != null) {
				BotDatabase.MobileAuthenticator.Init(this);
			} else {
				// Support and convert SDA files
				string maFilePath = botPath + ".maFile";
				if (File.Exists(maFilePath)) {
					ImportAuthenticator(maFilePath);
				}
			}

			// Initialize
			SteamClient = new SteamClient(Program.GlobalConfig.SteamProtocol);

			if (Program.GlobalConfig.Debug && !Debugging.NetHookAlreadyInitialized && Directory.Exists(Program.DebugDirectory)) {
				try {
					Debugging.NetHookAlreadyInitialized = true;
					SteamClient.DebugNetworkListener = new NetHookNetworkListener(Program.DebugDirectory);
				} catch (Exception e) {
					Logging.LogGenericException(e, botName);
				}
			}

			ArchiHandler = new ArchiHandler(this);
			SteamClient.AddHandler(ArchiHandler);

			CallbackManager = new CallbackManager(SteamClient);
			CallbackManager.Subscribe<SteamClient.ConnectedCallback>(OnConnected);
			CallbackManager.Subscribe<SteamClient.DisconnectedCallback>(OnDisconnected);

			SteamApps = SteamClient.GetHandler<SteamApps>();
			CallbackManager.Subscribe<SteamApps.FreeLicenseCallback>(OnFreeLicense);
			CallbackManager.Subscribe<SteamApps.GuestPassListCallback>(OnGuestPassList);

			SteamFriends = SteamClient.GetHandler<SteamFriends>();
			CallbackManager.Subscribe<SteamFriends.ChatInviteCallback>(OnChatInvite);
			CallbackManager.Subscribe<SteamFriends.ChatMsgCallback>(OnChatMsg);
			CallbackManager.Subscribe<SteamFriends.FriendsListCallback>(OnFriendsList);
			CallbackManager.Subscribe<SteamFriends.FriendMsgCallback>(OnFriendMsg);
			CallbackManager.Subscribe<SteamFriends.FriendMsgHistoryCallback>(OnFriendMsgHistory);

			SteamUser = SteamClient.GetHandler<SteamUser>();
			CallbackManager.Subscribe<SteamUser.AccountInfoCallback>(OnAccountInfo);
			CallbackManager.Subscribe<SteamUser.LoggedOffCallback>(OnLoggedOff);
			CallbackManager.Subscribe<SteamUser.LoggedOnCallback>(OnLoggedOn);
			CallbackManager.Subscribe<SteamUser.LoginKeyCallback>(OnLoginKey);
			CallbackManager.Subscribe<SteamUser.UpdateMachineAuthCallback>(OnMachineAuth);
			CallbackManager.Subscribe<SteamUser.WebAPIUserNonceCallback>(OnWebAPIUserNonce);

			CallbackManager.Subscribe<ArchiHandler.NotificationsCallback>(OnNotifications);
			CallbackManager.Subscribe<ArchiHandler.OfflineMessageCallback>(OnOfflineMessage);
			CallbackManager.Subscribe<ArchiHandler.PlayingSessionStateCallback>(OnPlayingSessionState);
			CallbackManager.Subscribe<ArchiHandler.PurchaseResponseCallback>(OnPurchaseResponse);

			ArchiWebHandler = new ArchiWebHandler(this);
			CardsFarmer = new CardsFarmer(this);
			Trading = new Trading(this);

			if ((AcceptConfirmationsTimer == null) && (BotConfig.AcceptConfirmationsPeriod > 0)) {
				AcceptConfirmationsTimer = new Timer(
					async e => await AcceptConfirmations(true).ConfigureAwait(false),
					null,
					TimeSpan.FromMinutes(BotConfig.AcceptConfirmationsPeriod), // Delay
					TimeSpan.FromMinutes(BotConfig.AcceptConfirmationsPeriod) // Period
				);
			}

			if ((SendItemsTimer == null) && (BotConfig.SendTradePeriod > 0)) {
				SendItemsTimer = new Timer(
					async e => await ResponseLoot(BotConfig.SteamMasterID).ConfigureAwait(false),
					null,
					TimeSpan.FromHours(BotConfig.SendTradePeriod), // Delay
					TimeSpan.FromHours(BotConfig.SendTradePeriod) // Period
				);
			}

			// Register bot as available for ASF
			Bots[botName] = this;

			if (!BotConfig.StartOnLaunch) {
				return;
			}

			// Start
			Start().Forget();
		}
Example #12
0
        private async Task <bool> MatchActivelyRound(IReadOnlyCollection <Steam.Asset.EType> acceptedMatchableTypes)
        {
            if ((acceptedMatchableTypes == null) || (acceptedMatchableTypes.Count == 0))
            {
                Bot.ArchiLogger.LogNullError(nameof(acceptedMatchableTypes));
                return(false);
            }

            HashSet <Steam.Asset> ourInventory = await Bot.ArchiWebHandler.GetInventory(Bot.SteamID, tradable : true, wantedTypes : acceptedMatchableTypes).ConfigureAwait(false);

            if ((ourInventory == null) || (ourInventory.Count == 0))
            {
                Bot.ArchiLogger.LogGenericTrace(string.Format(Strings.ErrorIsEmpty, nameof(ourInventory)));
                return(false);
            }

            Dictionary <(uint AppID, Steam.Asset.EType Type), Dictionary <ulong, uint> > ourInventoryState = Trading.GetInventoryState(ourInventory);

            if (ourInventoryState.Values.All(set => set.Values.All(amount => amount <= 1)))
            {
                // User doesn't have any more dupes in the inventory
                Bot.ArchiLogger.LogGenericTrace(string.Format(Strings.ErrorIsEmpty, nameof(ourInventoryState)));
                return(false);
            }

            HashSet <ListedUser> listedUsers = await GetListedUsers().ConfigureAwait(false);

            if ((listedUsers == null) || (listedUsers.Count == 0))
            {
                Bot.ArchiLogger.LogGenericTrace(string.Format(Strings.ErrorIsEmpty, nameof(listedUsers)));
                return(false);
            }

            byte emptyMatches = 0;
            HashSet <(uint AppID, Steam.Asset.EType Type)> skippedSetsThisRound = new HashSet <(uint AppID, Steam.Asset.EType Type)>();

            foreach (ListedUser listedUser in listedUsers.Where(listedUser => listedUser.MatchEverything && acceptedMatchableTypes.Any(listedUser.MatchableTypes.Contains) && !Bot.IsBlacklistedFromTrades(listedUser.SteamID)).OrderByDescending(listedUser => listedUser.Score).Take(MaxMatchedBotsHard))
            {
                Bot.ArchiLogger.LogGenericTrace(listedUser.SteamID + "...");

                HashSet <Steam.Asset> theirInventory = await Bot.ArchiWebHandler.GetInventory(listedUser.SteamID, tradable : true, wantedSets : ourInventoryState.Keys, skippedSets : skippedSetsThisRound).ConfigureAwait(false);

                if ((theirInventory == null) || (theirInventory.Count == 0))
                {
                    Bot.ArchiLogger.LogGenericTrace(string.Format(Strings.ErrorIsEmpty, nameof(theirInventory)));
                    continue;
                }

                HashSet <(uint AppID, Steam.Asset.EType Type)> skippedSetsThisUser = new HashSet <(uint AppID, Steam.Asset.EType Type)>();
                Dictionary <(uint AppID, Steam.Asset.EType Type), Dictionary <ulong, uint> > theirInventoryState = Trading.GetInventoryState(theirInventory);

                for (byte i = 0; i < Trading.MaxTradesPerAccount; i++)
                {
                    byte itemsInTrade = 0;

                    Dictionary <ulong, uint> classIDsToGive    = new Dictionary <ulong, uint>();
                    Dictionary <ulong, uint> classIDsToReceive = new Dictionary <ulong, uint>();

                    foreach (KeyValuePair <(uint AppID, Steam.Asset.EType Type), Dictionary <ulong, uint> > ourInventoryStateSet in ourInventoryState.Where(set => listedUser.MatchableTypes.Contains(set.Key.Type) && set.Value.Values.Any(count => count > 1)))
                    {
                        if (!theirInventoryState.TryGetValue(ourInventoryStateSet.Key, out Dictionary <ulong, uint> theirItems))
                        {
                            continue;
                        }

                        bool match;

                        do
                        {
                            match = false;

                            foreach (KeyValuePair <ulong, uint> ourItem in ourInventoryStateSet.Value.Where(item => item.Value > 1).OrderByDescending(item => item.Value))
                            {
                                foreach (KeyValuePair <ulong, uint> theirItem in theirItems.OrderBy(item => ourInventoryStateSet.Value.TryGetValue(item.Key, out uint ourAmount) ? ourAmount : 0))
                                {
                                    if (ourInventoryStateSet.Value.TryGetValue(theirItem.Key, out uint ourAmountOfTheirItem) && (ourItem.Value <= ourAmountOfTheirItem + 1))
                                    {
                                        continue;
                                    }

                                    // Skip this set from the remaining of this round
                                    skippedSetsThisUser.Add(ourInventoryStateSet.Key);

                                    // Update our state based on given items
                                    classIDsToGive[ourItem.Key]             = classIDsToGive.TryGetValue(ourItem.Key, out uint givenAmount) ? givenAmount + 1 : 1;
                                    ourInventoryStateSet.Value[ourItem.Key] = ourItem.Value - 1;

                                    // Update our state based on received items
                                    classIDsToReceive[theirItem.Key]          = classIDsToReceive.TryGetValue(theirItem.Key, out uint receivedAmount) ? receivedAmount + 1 : 1;
                                    ourInventoryStateSet.Value[theirItem.Key] = ourAmountOfTheirItem + 1;

                                    // Update their state based on taken items
                                    if (theirItems.TryGetValue(theirItem.Key, out uint theirAmount) && (theirAmount > 1))
                                    {
                                        theirItems[theirItem.Key] = theirAmount - 1;
                                    }
                                    else
                                    {
                                        theirItems.Remove(theirItem.Key);
                                    }

                                    itemsInTrade += 2;

                                    match = true;
                                    break;
                                }

                                if (match)
                                {
                                    break;
                                }
                            }
                        } while (match && (itemsInTrade < Trading.MaxItemsPerTrade - 1));

                        if (itemsInTrade >= Trading.MaxItemsPerTrade - 1)
                        {
                            break;
                        }
                    }

                    if ((classIDsToGive.Count == 0) && (classIDsToReceive.Count == 0))
                    {
                        Bot.ArchiLogger.LogGenericTrace(string.Format(Strings.ErrorIsEmpty, nameof(classIDsToGive)));

                        if (++emptyMatches >= MaxMatchesBotsSoft)
                        {
                            Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.ActivelyMatchingItemsRound, skippedSetsThisRound.Count));
                            return(skippedSetsThisRound.Count > 0);
                        }

                        break;
                    }

                    emptyMatches = 0;

                    HashSet <Steam.Asset> itemsToGive    = Trading.GetItemsFromInventory(ourInventory, classIDsToGive);
                    HashSet <Steam.Asset> itemsToReceive = Trading.GetItemsFromInventory(theirInventory, classIDsToReceive);

                    Bot.ArchiLogger.LogGenericTrace(Bot.SteamID + " <- " + string.Join(", ", itemsToReceive.Select(item => item.RealAppID + "/" + item.Type + "-" + item.ClassID + " #" + item.Amount)) + " | " + string.Join(", ", itemsToGive.Select(item => item.RealAppID + "/" + item.Type + "-" + item.ClassID + " #" + item.Amount)) + " -> " + listedUser.SteamID);

                    (bool success, HashSet <ulong> mobileTradeOfferIDs) = await Bot.ArchiWebHandler.SendTradeOffer(listedUser.SteamID, itemsToGive, itemsToReceive, listedUser.TradeToken, true).ConfigureAwait(false);

                    if ((mobileTradeOfferIDs != null) && (mobileTradeOfferIDs.Count > 0) && Bot.HasMobileAuthenticator)
                    {
                        if (!await Bot.Actions.AcceptConfirmations(true, Steam.ConfirmationDetails.EType.Trade, listedUser.SteamID, mobileTradeOfferIDs, true).ConfigureAwait(false))
                        {
                            Bot.ArchiLogger.LogGenericTrace(Strings.WarningFailed);
                            return(false);
                        }
                    }

                    if (!success)
                    {
                        Bot.ArchiLogger.LogGenericTrace(Strings.WarningFailed);
                        continue;
                    }

                    Bot.ArchiLogger.LogGenericTrace(Strings.Success);
                }

                if (skippedSetsThisUser.Count == 0)
                {
                    continue;
                }

                skippedSetsThisRound.UnionWith(skippedSetsThisUser);

                foreach ((uint AppID, Steam.Asset.EType Type)skippedSet in skippedSetsThisUser)
                {
                    ourInventoryState.Remove(skippedSet);
                }

                if (ourInventoryState.Values.All(set => set.Values.All(amount => amount <= 1)))
                {
                    // User doesn't have any more dupes in the inventory
                    Bot.ArchiLogger.LogGenericTrace(string.Format(Strings.ErrorIsEmpty, nameof(ourInventoryState)));
                    break;
                }
            }

            Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.ActivelyMatchingItemsRound, skippedSetsThisRound.Count));
            return(skippedSetsThisRound.Count > 0);
        }
Example #13
0
		internal Bot(string botName) {
			if (string.IsNullOrEmpty(botName)) {
				throw new ArgumentNullException("botName");
			}

			BotName = botName;

			string botPath = Path.Combine(Program.ConfigDirectory, botName);

			BotConfig = BotConfig.Load(botPath + ".json");
			if (BotConfig == null) {
				Logging.LogGenericError("Your bot config is invalid, refusing to start this bot instance!", botName);
				return;
			}

			if (!BotConfig.Enabled) {
				return;
			}

			BotDatabase = BotDatabase.Load(botPath + ".db");
			if (BotDatabase == null) {
				Logging.LogGenericError("Bot database could not be loaded, refusing to start this bot instance!", botName);
				return;
			}

			bool alreadyExists;
			lock (Bots) {
				alreadyExists = Bots.ContainsKey(botName);
				if (!alreadyExists) {
					Bots[botName] = this;
				}
			}

			if (alreadyExists) {
				return;
			}

			SentryFile = botPath + ".bin";

			if (BotDatabase.SteamGuardAccount == null) {
				// Support and convert SDA files
				string maFilePath = botPath + ".maFile";
				if (File.Exists(maFilePath)) {
					ImportAuthenticator(maFilePath);
				}
			}

			// Initialize
			SteamClient = new SteamClient(Program.GlobalConfig.SteamProtocol);

			if (Program.GlobalConfig.Debug && !Debugging.NetHookAlreadyInitialized && Directory.Exists(Program.DebugDirectory)) {
				try {
					SteamClient.DebugNetworkListener = new NetHookNetworkListener(Program.DebugDirectory);
					Debugging.NetHookAlreadyInitialized = true;
				} catch (Exception e) {
					Logging.LogGenericException(e, botName);
				}
			}

			ArchiHandler = new ArchiHandler(this);
			SteamClient.AddHandler(ArchiHandler);

			CallbackManager = new CallbackManager(SteamClient);
			CallbackManager.Subscribe<SteamClient.ConnectedCallback>(OnConnected);
			CallbackManager.Subscribe<SteamClient.DisconnectedCallback>(OnDisconnected);

			SteamApps = SteamClient.GetHandler<SteamApps>();
			CallbackManager.Subscribe<SteamApps.FreeLicenseCallback>(OnFreeLicense);
			CallbackManager.Subscribe<SteamApps.GuestPassListCallback>(OnGuestPassList);

			SteamFriends = SteamClient.GetHandler<SteamFriends>();
			CallbackManager.Subscribe<SteamFriends.ChatInviteCallback>(OnChatInvite);
			CallbackManager.Subscribe<SteamFriends.ChatMsgCallback>(OnChatMsg);
			CallbackManager.Subscribe<SteamFriends.FriendsListCallback>(OnFriendsList);
			CallbackManager.Subscribe<SteamFriends.FriendMsgCallback>(OnFriendMsg);
			CallbackManager.Subscribe<SteamFriends.FriendMsgHistoryCallback>(OnFriendMsgHistory);

			SteamUser = SteamClient.GetHandler<SteamUser>();
			CallbackManager.Subscribe<SteamUser.AccountInfoCallback>(OnAccountInfo);
			CallbackManager.Subscribe<SteamUser.LoggedOffCallback>(OnLoggedOff);
			CallbackManager.Subscribe<SteamUser.LoggedOnCallback>(OnLoggedOn);
			CallbackManager.Subscribe<SteamUser.LoginKeyCallback>(OnLoginKey);
			CallbackManager.Subscribe<SteamUser.UpdateMachineAuthCallback>(OnMachineAuth);
			CallbackManager.Subscribe<SteamUser.WebAPIUserNonceCallback>(OnWebAPIUserNonce);

			CallbackManager.Subscribe<ArchiHandler.NotificationsCallback>(OnNotifications);
			CallbackManager.Subscribe<ArchiHandler.OfflineMessageCallback>(OnOfflineMessage);
			CallbackManager.Subscribe<ArchiHandler.PurchaseResponseCallback>(OnPurchaseResponse);

			ArchiWebHandler = new ArchiWebHandler(this);
			CardsFarmer = new CardsFarmer(this);
			Trading = new Trading(this);

			if (AcceptConfirmationsTimer == null && BotConfig.AcceptConfirmationsPeriod > 0) {
				AcceptConfirmationsTimer = new Timer(
					async e => await AcceptConfirmations(true).ConfigureAwait(false),
					null,
					TimeSpan.FromMinutes(BotConfig.AcceptConfirmationsPeriod), // Delay
					TimeSpan.FromMinutes(BotConfig.AcceptConfirmationsPeriod) // Period
				);
			}

			if (SendItemsTimer == null && BotConfig.SendTradePeriod > 0) {
				SendItemsTimer = new Timer(
					async e => await ResponseSendTrade(BotConfig.SteamMasterID).ConfigureAwait(false),
					null,
					TimeSpan.FromHours(BotConfig.SendTradePeriod), // Delay
					TimeSpan.FromHours(BotConfig.SendTradePeriod) // Period
				);
			}

			if (!BotConfig.StartOnLaunch) {
				return;
			}

			// Start
			Start().Forget();
		}