diff --git a/ArchiSteamFarm.sln.DotSettings b/ArchiSteamFarm.sln.DotSettings
index b084525c8..9a21d2744 100644
--- a/ArchiSteamFarm.sln.DotSettings
+++ b/ArchiSteamFarm.sln.DotSettings
@@ -90,10 +90,14 @@
END_OF_LINE
True
END_OF_LINE
+ 1
END_OF_LINE
TOGETHER_SAME_LINE
END_OF_LINE
END_OF_LINE
+ 1
+ 1
+ False
END_OF_LINE
False
False
@@ -102,9 +106,216 @@
False
LINE_BREAK
True
+ True
END_OF_LINE
- 255
+ 1000
False
+ <?xml version="1.0" encoding="utf-16"?>
+<Patterns xmlns="urn:schemas-jetbrains-com:member-reordering-patterns">
+ <TypePattern DisplayName="ArchiPattern" Priority="150">
+ <Entry DisplayName="Public (Events and Delegates)">
+ <Entry.Match>
+ <And>
+ <Access Is="Public" />
+ <Or>
+ <Kind Is="Delegate" />
+ <Kind Is="Event" />
+ </Or>
+ </And>
+ </Entry.Match>
+ <Entry.SortBy>
+ <Access />
+ <Kind Is="Member" />
+ <Name />
+ </Entry.SortBy>
+ </Entry>
+ <Entry DisplayName="Constants">
+ <Entry.Match>
+ <Kind Is="Constant" />
+ </Entry.Match>
+ <Entry.SortBy>
+ <Access />
+ <Kind Is="Member" />
+ <Name />
+ </Entry.SortBy>
+ </Entry>
+ <Entry DisplayName="Static (Fields, Properties and Indexers)">
+ <Entry.Match>
+ <Or>
+ <And>
+ <Kind Is="Field" />
+ <Static />
+ </And>
+ <And>
+ <Kind Is="Autoproperty" />
+ <Static />
+ </And>
+ <And>
+ <Kind Is="Property" />
+ <Static />
+ </And>
+ <And>
+ <Kind Is="Indexer" />
+ <Static />
+ </And>
+ </Or>
+ </Entry.Match>
+ <Entry.SortBy>
+ <Access />
+ <Readonly />
+ <Kind Is="Member" />
+ <Name />
+ </Entry.SortBy>
+ </Entry>
+ <Entry DisplayName="Non-static (Fields, Properties and Indexers)">
+ <Entry.Match>
+ <And>
+ <Not>
+ <Static />
+ </Not>
+ <Or>
+ <Kind Is="Field" />
+ <Kind Is="Autoproperty" />
+ <Kind Is="Property" />
+ <Kind Is="Indexer" />
+ </Or>
+ </And>
+ </Entry.Match>
+ <Entry.SortBy>
+ <Readonly />
+ <Access />
+ <Kind Is="Member" />
+ <Name />
+ </Entry.SortBy>
+ </Entry>
+ <Entry DisplayName="Constructors">
+ <Entry.Match>
+ <Kind Is="Constructor" />
+ </Entry.Match>
+ <Entry.SortBy>
+ <Access />
+ <Kind Is="Member" />
+ <Name />
+ </Entry.SortBy>
+ </Entry>
+ <Entry DisplayName="Interfaces">
+ <Entry.Match>
+ <And>
+ <Kind Is="Member" />
+ <ImplementsInterface />
+ </And>
+ </Entry.Match>
+ <Entry.SortBy>
+ <Access />
+ <Kind Is="Member" />
+ <Name />
+ </Entry.SortBy>
+ </Entry>
+ <Entry DisplayName="Everything else">
+ <Entry.SortBy>
+ <Access />
+ <Kind Is="Member" />
+ <Name />
+ </Entry.SortBy>
+ </Entry>
+ <Entry DisplayName="Nested">
+ <Entry.Match>
+ <Kind Is="Type" />
+ </Entry.Match>
+ <Entry.SortBy>
+ <Access />
+ <Kind Is="Member" />
+ <Name />
+ </Entry.SortBy>
+ </Entry>
+ </TypePattern>
+ <TypePattern DisplayName="COM interfaces or structs">
+ <TypePattern.Match>
+ <Or>
+ <And>
+ <Kind Is="Interface" />
+ <Or>
+ <HasAttribute Name="System.Runtime.InteropServices.InterfaceTypeAttribute" />
+ <HasAttribute Name="System.Runtime.InteropServices.ComImport" />
+ </Or>
+ </And>
+ <Kind Is="Struct" />
+ </Or>
+ </TypePattern.Match>
+ </TypePattern>
+ <TypePattern DisplayName="xUnit.net Test Classes" RemoveRegions="All">
+ <TypePattern.Match>
+ <And>
+ <Kind Is="Class" />
+ <HasMember>
+ <And>
+ <Kind Is="Method" />
+ <HasAttribute Name="Xunit.FactAttribute" Inherited="True" />
+ </And>
+ </HasMember>
+ </And>
+ </TypePattern.Match>
+ <Entry DisplayName="Setup/Teardown Methods">
+ <Entry.Match>
+ <Or>
+ <Kind Is="Constructor" />
+ <And>
+ <Kind Is="Method" />
+ <ImplementsInterface Name="System.IDisposable" />
+ </And>
+ </Or>
+ </Entry.Match>
+ <Entry.SortBy>
+ <Kind Order="Constructor" />
+ </Entry.SortBy>
+ </Entry>
+ <Entry DisplayName="All other members" />
+ <Entry DisplayName="Test Methods" Priority="100">
+ <Entry.Match>
+ <And>
+ <Kind Is="Method" />
+ <HasAttribute Name="Xunit.FactAttribute" />
+ </And>
+ </Entry.Match>
+ <Entry.SortBy>
+ <Name />
+ </Entry.SortBy>
+ </Entry>
+ </TypePattern>
+ <TypePattern DisplayName="NUnit Test Fixtures" RemoveRegions="All">
+ <TypePattern.Match>
+ <And>
+ <Kind Is="Class" />
+ <HasAttribute Name="NUnit.Framework.TestFixtureAttribute" Inherited="True" />
+ </And>
+ </TypePattern.Match>
+ <Entry DisplayName="Setup/Teardown Methods">
+ <Entry.Match>
+ <And>
+ <Kind Is="Method" />
+ <Or>
+ <HasAttribute Name="NUnit.Framework.SetUpAttribute" Inherited="True" />
+ <HasAttribute Name="NUnit.Framework.TearDownAttribute" Inherited="True" />
+ <HasAttribute Name="NUnit.Framework.FixtureSetUpAttribute" Inherited="True" />
+ <HasAttribute Name="NUnit.Framework.FixtureTearDownAttribute" Inherited="True" />
+ </Or>
+ </And>
+ </Entry.Match>
+ </Entry>
+ <Entry DisplayName="All other members" />
+ <Entry DisplayName="Test Methods" Priority="100">
+ <Entry.Match>
+ <And>
+ <Kind Is="Method" />
+ <HasAttribute Name="NUnit.Framework.TestAttribute" />
+ </And>
+ </Entry.Match>
+ <Entry.SortBy>
+ <Name />
+ </Entry.SortBy>
+ </Entry>
+ </TypePattern>
+</Patterns>
UseExplicitType
UseExplicitType
UseExplicitType
@@ -129,5 +340,6 @@
<Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="_" Suffix="" Style="AaBb" /><ExtraRule Prefix="_" Suffix="" Style="aaBb" /></Policy>
<Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="_" Suffix="" Style="AaBb" /><ExtraRule Prefix="_" Suffix="" Style="aaBb" /></Policy>
True
+ True
True
True
\ No newline at end of file
diff --git a/ArchiSteamFarm/ASF.cs b/ArchiSteamFarm/ASF.cs
index 04a93e935..b2be71eaf 100644
--- a/ArchiSteamFarm/ASF.cs
+++ b/ArchiSteamFarm/ASF.cs
@@ -34,28 +34,6 @@ using ArchiSteamFarm.JSON;
namespace ArchiSteamFarm {
internal static class ASF {
- internal sealed class BotConfigEventArgs : EventArgs {
- internal readonly BotConfig BotConfig;
-
- internal BotConfigEventArgs(BotConfig botConfig = null) {
- BotConfig = botConfig;
- }
- }
-
- internal enum EUserInputType : byte {
- Unknown,
- DeviceID,
- Login,
- Password,
- PhoneNumber,
- SMS,
- SteamGuard,
- SteamParentalPIN,
- RevocationCode,
- TwoFactorAuthentication,
- WCFHostname
- }
-
internal static readonly ArchiLogger ArchiLogger = new ArchiLogger(SharedInfo.ASF);
private static readonly ConcurrentDictionary LastWriteTimes = new ConcurrentDictionary();
@@ -90,10 +68,7 @@ namespace ArchiSteamFarm {
}
if ((AutoUpdatesTimer == null) && Program.GlobalConfig.AutoUpdates) {
- AutoUpdatesTimer = new Timer(
- async e => await CheckForUpdate().ConfigureAwait(false),
- null,
- TimeSpan.FromDays(1), // Delay
+ AutoUpdatesTimer = new Timer(async e => await CheckForUpdate().ConfigureAwait(false), null, TimeSpan.FromDays(1), // Delay
TimeSpan.FromDays(1) // Period
);
@@ -258,9 +233,7 @@ namespace ArchiSteamFarm {
return;
}
- FileSystemWatcher = new FileSystemWatcher(SharedInfo.ConfigDirectory, "*.json") {
- NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite
- };
+ FileSystemWatcher = new FileSystemWatcher(SharedInfo.ConfigDirectory, "*.json") { NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite };
FileSystemWatcher.Changed += OnChanged;
FileSystemWatcher.Created += OnCreated;
@@ -408,5 +381,27 @@ namespace ArchiSteamFarm {
CreateBot(newBotName).Forget();
}
+
+ internal sealed class BotConfigEventArgs : EventArgs {
+ internal readonly BotConfig BotConfig;
+
+ internal BotConfigEventArgs(BotConfig botConfig = null) {
+ BotConfig = botConfig;
+ }
+ }
+
+ internal enum EUserInputType : byte {
+ Unknown,
+ DeviceID,
+ Login,
+ Password,
+ PhoneNumber,
+ SMS,
+ SteamGuard,
+ SteamParentalPIN,
+ RevocationCode,
+ TwoFactorAuthentication,
+ WCFHostname
+ }
}
-}
+}
\ No newline at end of file
diff --git a/ArchiSteamFarm/App.config b/ArchiSteamFarm/App.config
index fd9961fb2..efb4d02fd 100644
--- a/ArchiSteamFarm/App.config
+++ b/ArchiSteamFarm/App.config
@@ -1,6 +1,7 @@
-
+
+
-
-
-
-
+
+
+
+
\ No newline at end of file
diff --git a/ArchiSteamFarm/ArchiHandler.cs b/ArchiSteamFarm/ArchiHandler.cs
index c2da0e33e..fea520c72 100644
--- a/ArchiSteamFarm/ArchiHandler.cs
+++ b/ArchiSteamFarm/ArchiHandler.cs
@@ -22,8 +22,6 @@
*/
-using SteamKit2;
-using SteamKit2.Internal;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
@@ -31,6 +29,8 @@ using System.IO;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
+using SteamKit2;
+using SteamKit2.Internal;
namespace ArchiSteamFarm {
internal sealed class ArchiHandler : ClientMsgHandler {
@@ -44,187 +44,37 @@ namespace ArchiSteamFarm {
ArchiLogger = archiLogger;
}
- /*
- ____ _ _ _ _
- / ___| __ _ | || || |__ __ _ ___ | | __ ___
- | | / _` || || || '_ \ / _` | / __|| |/ // __|
- | |___| (_| || || || |_) || (_| || (__ | < \__ \
- \____|\__,_||_||_||_.__/ \__,_| \___||_|\_\|___/
-
- */
-
- internal sealed class NotificationsCallback : CallbackMsg {
- internal enum ENotification : byte {
- [SuppressMessage("ReSharper", "UnusedMember.Global")]
- Unknown = 0,
- Trading = 1,
- // Only custom below, different than ones available as user_notification_type
- Items = 254
+ public override void HandleMsg(IPacketMsg packetMsg) {
+ if (packetMsg == null) {
+ ArchiLogger.LogNullError(nameof(packetMsg));
+ return;
}
- internal readonly HashSet Notifications;
-
- internal NotificationsCallback(JobID jobID, CMsgClientUserNotifications msg) {
- if ((jobID == null) || (msg == null)) {
- throw new ArgumentNullException(nameof(jobID) + " || " + nameof(msg));
- }
-
- JobID = jobID;
-
- if (msg.notifications.Count == 0) {
- return;
- }
-
- Notifications = new HashSet(msg.notifications.Select(notification => (ENotification) notification.user_notification_type));
- }
-
- internal NotificationsCallback(JobID jobID, CMsgClientItemAnnouncements msg) {
- if ((jobID == null) || (msg == null)) {
- throw new ArgumentNullException(nameof(jobID) + " || " + nameof(msg));
- }
-
- JobID = jobID;
-
- if (msg.count_new_items > 0) {
- Notifications = new HashSet { ENotification.Items };
- }
+ switch (packetMsg.MsgType) {
+ case EMsg.ClientFSOfflineMessageNotification:
+ HandleFSOfflineMessageNotification(packetMsg);
+ break;
+ case EMsg.ClientItemAnnouncements:
+ HandleItemAnnouncements(packetMsg);
+ break;
+ case EMsg.ClientPlayingSessionState:
+ HandlePlayingSessionState(packetMsg);
+ break;
+ case EMsg.ClientPurchaseResponse:
+ HandlePurchaseResponse(packetMsg);
+ break;
+ case EMsg.ClientRedeemGuestPassResponse:
+ HandleRedeemGuestPassResponse(packetMsg);
+ break;
+ case EMsg.ClientSharedLibraryLockStatus:
+ HandleSharedLibraryLockStatus(packetMsg);
+ break;
+ case EMsg.ClientUserNotifications:
+ HandleUserNotifications(packetMsg);
+ break;
}
}
- internal sealed class OfflineMessageCallback : CallbackMsg {
- internal readonly uint OfflineMessagesCount;
-
- internal OfflineMessageCallback(JobID jobID, CMsgClientOfflineMessageNotification msg) {
- if ((jobID == null) || (msg == null)) {
- throw new ArgumentNullException(nameof(jobID) + " || " + nameof(msg));
- }
-
- JobID = jobID;
- OfflineMessagesCount = msg.offline_messages;
- }
- }
-
- internal sealed class PlayingSessionStateCallback : CallbackMsg {
- internal readonly bool PlayingBlocked;
-
- internal PlayingSessionStateCallback(JobID jobID, CMsgClientPlayingSessionState msg) {
- if ((jobID == null) || (msg == null)) {
- throw new ArgumentNullException(nameof(jobID) + " || " + nameof(msg));
- }
-
- JobID = jobID;
- PlayingBlocked = msg.playing_blocked;
- }
- }
-
- internal sealed class PurchaseResponseCallback : CallbackMsg {
- internal enum EPurchaseResult : sbyte {
- [SuppressMessage("ReSharper", "UnusedMember.Global")]
- Unknown = -2,
- Timeout = -1,
- OK = 0,
- AlreadyOwned = 9,
- RegionLocked = 13,
- InvalidKey = 14,
- DuplicatedKey = 15,
- BaseGameRequired = 24,
- SteamWalletCode = 50,
- OnCooldown = 53
- }
-
- internal readonly Dictionary Items;
-
- internal EPurchaseResult PurchaseResult { get; set; }
-
- internal PurchaseResponseCallback(JobID jobID, CMsgClientPurchaseResponse msg) {
- if ((jobID == null) || (msg == null)) {
- throw new ArgumentNullException(nameof(jobID) + " || " + nameof(msg));
- }
-
- JobID = jobID;
- PurchaseResult = (EPurchaseResult) msg.purchase_result_details;
-
- if (msg.purchase_receipt_info == null) {
- return;
- }
-
- KeyValue receiptInfo = new KeyValue();
- using (MemoryStream ms = new MemoryStream(msg.purchase_receipt_info)) {
- if (!receiptInfo.TryReadAsBinary(ms)) {
- ASF.ArchiLogger.LogNullError(nameof(ms));
- return;
- }
- }
-
- List lineItems = receiptInfo["lineitems"].Children;
- if (lineItems.Count == 0) {
- return;
- }
-
- Items = new Dictionary(lineItems.Count);
- foreach (KeyValue lineItem in lineItems) {
- uint packageID = lineItem["PackageID"].AsUnsignedInteger();
- if (packageID == 0) {
- // Valid, coupons have PackageID of -1 (don't ask me why)
- packageID = lineItem["ItemAppID"].AsUnsignedInteger();
- if (packageID == 0) {
- ASF.ArchiLogger.LogNullError(nameof(packageID));
- return;
- }
- }
-
- string gameName = lineItem["ItemDescription"].Value;
- if (string.IsNullOrEmpty(gameName)) {
- ASF.ArchiLogger.LogNullError(nameof(gameName));
- return;
- }
-
- gameName = WebUtility.HtmlDecode(gameName); // Apparently steam expects client to decode sent HTML
- Items[packageID] = WebUtility.HtmlDecode(gameName);
- }
- }
- }
-
- internal sealed class RedeemGuestPassResponseCallback : CallbackMsg {
- internal readonly EResult Result;
-
- internal RedeemGuestPassResponseCallback(JobID jobID, CMsgClientRedeemGuestPassResponse msg) {
- if ((jobID == null) || (msg == null)) {
- throw new ArgumentNullException(nameof(jobID) + " || " + nameof(msg));
- }
-
- JobID = jobID;
- Result = (EResult) msg.eresult;
- }
- }
-
- internal sealed class SharedLibraryLockStatusCallback : CallbackMsg {
- internal readonly ulong LibraryLockedBySteamID;
-
- internal SharedLibraryLockStatusCallback(JobID jobID, CMsgClientSharedLibraryLockStatus msg) {
- if ((jobID == null) || (msg == null)) {
- throw new ArgumentNullException(nameof(jobID) + " || " + nameof(msg));
- }
-
- JobID = jobID;
-
- if (msg.own_library_locked_by == 0) {
- return;
- }
-
- LibraryLockedBySteamID = new SteamID(msg.own_library_locked_by, EUniverse.Public, EAccountType.Individual);
- }
- }
-
- /*
- __ __ _ _ _
- | \/ | ___ | |_ | |__ ___ __| | ___
- | |\/| | / _ \| __|| '_ \ / _ \ / _` |/ __|
- | | | || __/| |_ | | | || (_) || (_| |\__ \
- |_| |_| \___| \__||_| |_| \___/ \__,_||___/
-
- */
-
// TODO: Remove me once https://github.com/SteamRE/SteamKit/issues/305 is fixed
internal void LogOnWithoutMachineID(SteamUser.LogOnDetails details) {
if (details == null) {
@@ -276,7 +126,6 @@ namespace ArchiSteamFarm {
logon.Body.sha_sentryfile = details.SentryFileHash;
logon.Body.eresult_sentryfile = (int) (details.SentryFileHash != null ? EResult.OK : EResult.FileNotFound);
-
Client.Send(logon);
}
@@ -301,19 +150,11 @@ namespace ArchiSteamFarm {
ClientMsgProtobuf request = new ClientMsgProtobuf(EMsg.ClientGamesPlayed);
if (!string.IsNullOrEmpty(gameName)) {
- request.Body.games_played.Add(new CMsgClientGamesPlayed.GamePlayed {
- game_extra_info = gameName,
- game_id = new GameID {
- AppType = GameID.GameType.Shortcut,
- ModID = uint.MaxValue
- }
- });
+ request.Body.games_played.Add(new CMsgClientGamesPlayed.GamePlayed { game_extra_info = gameName, game_id = new GameID { AppType = GameID.GameType.Shortcut, ModID = uint.MaxValue } });
}
foreach (uint gameID in gameIDs.Where(gameID => gameID != 0)) {
- request.Body.games_played.Add(new CMsgClientGamesPlayed.GamePlayed {
- game_id = new GameID(gameID)
- });
+ request.Body.games_played.Add(new CMsgClientGamesPlayed.GamePlayed { game_id = new GameID(gameID) });
}
Client.Send(request);
@@ -329,9 +170,7 @@ namespace ArchiSteamFarm {
return null;
}
- ClientMsgProtobuf request = new ClientMsgProtobuf(EMsg.ClientRedeemGuestPass) {
- SourceJobID = Client.GetNextJobID()
- };
+ ClientMsgProtobuf request = new ClientMsgProtobuf(EMsg.ClientRedeemGuestPass) { SourceJobID = Client.GetNextJobID() };
request.Body.guest_pass_id = guestPassID;
@@ -355,9 +194,7 @@ namespace ArchiSteamFarm {
return null;
}
- ClientMsgProtobuf request = new ClientMsgProtobuf(EMsg.ClientRegisterKey) {
- SourceJobID = Client.GetNextJobID()
- };
+ ClientMsgProtobuf request = new ClientMsgProtobuf(EMsg.ClientRegisterKey) { SourceJobID = Client.GetNextJobID() };
request.Body.key = key;
@@ -371,46 +208,6 @@ namespace ArchiSteamFarm {
}
}
- /*
- _ _ _ _
- | | | | __ _ _ __ __| || | ___ _ __ ___
- | |_| | / _` || '_ \ / _` || | / _ \| '__|/ __|
- | _ || (_| || | | || (_| || || __/| | \__ \
- |_| |_| \__,_||_| |_| \__,_||_| \___||_| |___/
-
- */
-
- public override void HandleMsg(IPacketMsg packetMsg) {
- if (packetMsg == null) {
- ArchiLogger.LogNullError(nameof(packetMsg));
- return;
- }
-
- switch (packetMsg.MsgType) {
- case EMsg.ClientFSOfflineMessageNotification:
- HandleFSOfflineMessageNotification(packetMsg);
- break;
- case EMsg.ClientItemAnnouncements:
- HandleItemAnnouncements(packetMsg);
- break;
- case EMsg.ClientPlayingSessionState:
- HandlePlayingSessionState(packetMsg);
- break;
- case EMsg.ClientPurchaseResponse:
- HandlePurchaseResponse(packetMsg);
- break;
- case EMsg.ClientRedeemGuestPassResponse:
- HandleRedeemGuestPassResponse(packetMsg);
- break;
- case EMsg.ClientSharedLibraryLockStatus:
- HandleSharedLibraryLockStatus(packetMsg);
- break;
- case EMsg.ClientUserNotifications:
- HandleUserNotifications(packetMsg);
- break;
- }
- }
-
private void HandleFSOfflineMessageNotification(IPacketMsg packetMsg) {
if (packetMsg == null) {
ArchiLogger.LogNullError(nameof(packetMsg));
@@ -480,5 +277,168 @@ namespace ArchiSteamFarm {
ClientMsgProtobuf response = new ClientMsgProtobuf(packetMsg);
Client.PostCallback(new NotificationsCallback(packetMsg.TargetJobID, response.Body));
}
+
+ internal sealed class NotificationsCallback : CallbackMsg {
+ internal readonly HashSet Notifications;
+
+ internal NotificationsCallback(JobID jobID, CMsgClientUserNotifications msg) {
+ if ((jobID == null) || (msg == null)) {
+ throw new ArgumentNullException(nameof(jobID) + " || " + nameof(msg));
+ }
+
+ JobID = jobID;
+
+ if (msg.notifications.Count == 0) {
+ return;
+ }
+
+ Notifications = new HashSet(msg.notifications.Select(notification => (ENotification) notification.user_notification_type));
+ }
+
+ internal NotificationsCallback(JobID jobID, CMsgClientItemAnnouncements msg) {
+ if ((jobID == null) || (msg == null)) {
+ throw new ArgumentNullException(nameof(jobID) + " || " + nameof(msg));
+ }
+
+ JobID = jobID;
+
+ if (msg.count_new_items > 0) {
+ Notifications = new HashSet { ENotification.Items };
+ }
+ }
+
+ internal enum ENotification : byte {
+ [SuppressMessage("ReSharper", "UnusedMember.Global")]
+ Unknown = 0,
+ Trading = 1,
+ // Only custom below, different than ones available as user_notification_type
+ Items = 254
+ }
+ }
+
+ internal sealed class OfflineMessageCallback : CallbackMsg {
+ internal readonly uint OfflineMessagesCount;
+
+ internal OfflineMessageCallback(JobID jobID, CMsgClientOfflineMessageNotification msg) {
+ if ((jobID == null) || (msg == null)) {
+ throw new ArgumentNullException(nameof(jobID) + " || " + nameof(msg));
+ }
+
+ JobID = jobID;
+ OfflineMessagesCount = msg.offline_messages;
+ }
+ }
+
+ internal sealed class PlayingSessionStateCallback : CallbackMsg {
+ internal readonly bool PlayingBlocked;
+
+ internal PlayingSessionStateCallback(JobID jobID, CMsgClientPlayingSessionState msg) {
+ if ((jobID == null) || (msg == null)) {
+ throw new ArgumentNullException(nameof(jobID) + " || " + nameof(msg));
+ }
+
+ JobID = jobID;
+ PlayingBlocked = msg.playing_blocked;
+ }
+ }
+
+ internal sealed class PurchaseResponseCallback : CallbackMsg {
+ internal readonly Dictionary Items;
+
+ internal EPurchaseResult PurchaseResult { get; set; }
+
+ internal PurchaseResponseCallback(JobID jobID, CMsgClientPurchaseResponse msg) {
+ if ((jobID == null) || (msg == null)) {
+ throw new ArgumentNullException(nameof(jobID) + " || " + nameof(msg));
+ }
+
+ JobID = jobID;
+ PurchaseResult = (EPurchaseResult) msg.purchase_result_details;
+
+ if (msg.purchase_receipt_info == null) {
+ return;
+ }
+
+ KeyValue receiptInfo = new KeyValue();
+ using (MemoryStream ms = new MemoryStream(msg.purchase_receipt_info)) {
+ if (!receiptInfo.TryReadAsBinary(ms)) {
+ ASF.ArchiLogger.LogNullError(nameof(ms));
+ return;
+ }
+ }
+
+ List lineItems = receiptInfo["lineitems"].Children;
+ if (lineItems.Count == 0) {
+ return;
+ }
+
+ Items = new Dictionary(lineItems.Count);
+ foreach (KeyValue lineItem in lineItems) {
+ uint packageID = lineItem["PackageID"].AsUnsignedInteger();
+ if (packageID == 0) {
+ // Valid, coupons have PackageID of -1 (don't ask me why)
+ packageID = lineItem["ItemAppID"].AsUnsignedInteger();
+ if (packageID == 0) {
+ ASF.ArchiLogger.LogNullError(nameof(packageID));
+ return;
+ }
+ }
+
+ string gameName = lineItem["ItemDescription"].Value;
+ if (string.IsNullOrEmpty(gameName)) {
+ ASF.ArchiLogger.LogNullError(nameof(gameName));
+ return;
+ }
+
+ gameName = WebUtility.HtmlDecode(gameName); // Apparently steam expects client to decode sent HTML
+ Items[packageID] = WebUtility.HtmlDecode(gameName);
+ }
+ }
+
+ internal enum EPurchaseResult : sbyte {
+ [SuppressMessage("ReSharper", "UnusedMember.Global")]
+ Unknown = -2,
+ Timeout = -1,
+ OK = 0,
+ AlreadyOwned = 9,
+ RegionLocked = 13,
+ InvalidKey = 14,
+ DuplicatedKey = 15,
+ BaseGameRequired = 24,
+ SteamWalletCode = 50,
+ OnCooldown = 53
+ }
+ }
+
+ internal sealed class RedeemGuestPassResponseCallback : CallbackMsg {
+ internal readonly EResult Result;
+
+ internal RedeemGuestPassResponseCallback(JobID jobID, CMsgClientRedeemGuestPassResponse msg) {
+ if ((jobID == null) || (msg == null)) {
+ throw new ArgumentNullException(nameof(jobID) + " || " + nameof(msg));
+ }
+
+ JobID = jobID;
+ Result = (EResult) msg.eresult;
+ }
+ }
+
+ internal sealed class SharedLibraryLockStatusCallback : CallbackMsg {
+ internal readonly ulong LibraryLockedBySteamID;
+
+ internal SharedLibraryLockStatusCallback(JobID jobID, CMsgClientSharedLibraryLockStatus msg) {
+ if ((jobID == null) || (msg == null)) {
+ throw new ArgumentNullException(nameof(jobID) + " || " + nameof(msg));
+ }
+
+ JobID = jobID;
+
+ if (msg.own_library_locked_by == 0) {
+ return;
+ }
+
+ LibraryLockedBySteamID = new SteamID(msg.own_library_locked_by, EUniverse.Public, EAccountType.Individual);
+ }
+ }
}
}
\ No newline at end of file
diff --git a/ArchiSteamFarm/ArchiLogger.cs b/ArchiSteamFarm/ArchiLogger.cs
index 9ff07284f..56d88b3bc 100644
--- a/ArchiSteamFarm/ArchiLogger.cs
+++ b/ArchiSteamFarm/ArchiLogger.cs
@@ -40,24 +40,6 @@ namespace ArchiSteamFarm {
Logger = LogManager.GetLogger(name);
}
- internal void LogGenericError(string message, [CallerMemberName] string previousMethodName = null) {
- if (string.IsNullOrEmpty(message)) {
- LogNullError(nameof(message));
- return;
- }
-
- Logger.Error($"{previousMethodName}() {message}");
- }
-
- internal void LogGenericException(Exception exception, [CallerMemberName] string previousMethodName = null) {
- if (exception == null) {
- LogNullError(nameof(exception));
- return;
- }
-
- Logger.Error(exception, $"{previousMethodName}()");
- }
-
[SuppressMessage("ReSharper", "LocalizableElement")]
internal void LogFatalException(Exception exception, [CallerMemberName] string previousMethodName = null) {
if (exception == null) {
@@ -86,13 +68,31 @@ namespace ArchiSteamFarm {
}
}
- internal void LogGenericWarning(string message, [CallerMemberName] string previousMethodName = null) {
+ internal void LogGenericDebug(string message, [CallerMemberName] string previousMethodName = null) {
if (string.IsNullOrEmpty(message)) {
LogNullError(nameof(message));
return;
}
- Logger.Warn($"{previousMethodName}() {message}");
+ Logger.Debug($"{previousMethodName}() {message}");
+ }
+
+ internal void LogGenericError(string message, [CallerMemberName] string previousMethodName = null) {
+ if (string.IsNullOrEmpty(message)) {
+ LogNullError(nameof(message));
+ return;
+ }
+
+ Logger.Error($"{previousMethodName}() {message}");
+ }
+
+ internal void LogGenericException(Exception exception, [CallerMemberName] string previousMethodName = null) {
+ if (exception == null) {
+ LogNullError(nameof(exception));
+ return;
+ }
+
+ Logger.Error(exception, $"{previousMethodName}()");
}
internal void LogGenericInfo(string message, [CallerMemberName] string previousMethodName = null) {
@@ -104,24 +104,6 @@ namespace ArchiSteamFarm {
Logger.Info($"{previousMethodName}() {message}");
}
- [SuppressMessage("ReSharper", "ExplicitCallerInfoArgument")]
- internal void LogNullError(string nullObjectName, [CallerMemberName] string previousMethodName = null) {
- if (string.IsNullOrEmpty(nullObjectName)) {
- return;
- }
-
- LogGenericError(nullObjectName + " is null!", previousMethodName);
- }
-
- internal void LogGenericDebug(string message, [CallerMemberName] string previousMethodName = null) {
- if (string.IsNullOrEmpty(message)) {
- LogNullError(nameof(message));
- return;
- }
-
- Logger.Debug($"{previousMethodName}() {message}");
- }
-
internal void LogGenericTrace(string message, [CallerMemberName] string previousMethodName = null) {
if (string.IsNullOrEmpty(message)) {
LogNullError(nameof(message));
@@ -130,5 +112,23 @@ namespace ArchiSteamFarm {
Logger.Trace($"{previousMethodName}() {message}");
}
+
+ internal void LogGenericWarning(string message, [CallerMemberName] string previousMethodName = null) {
+ if (string.IsNullOrEmpty(message)) {
+ LogNullError(nameof(message));
+ return;
+ }
+
+ Logger.Warn($"{previousMethodName}() {message}");
+ }
+
+ [SuppressMessage("ReSharper", "ExplicitCallerInfoArgument")]
+ internal void LogNullError(string nullObjectName, [CallerMemberName] string previousMethodName = null) {
+ if (string.IsNullOrEmpty(nullObjectName)) {
+ return;
+ }
+
+ LogGenericError(nullObjectName + " is null!", previousMethodName);
+ }
}
-}
+}
\ No newline at end of file
diff --git a/ArchiSteamFarm/ArchiServiceInstaller.cs b/ArchiSteamFarm/ArchiServiceInstaller.cs
index 817193c37..aacbc6497 100644
--- a/ArchiSteamFarm/ArchiServiceInstaller.cs
+++ b/ArchiSteamFarm/ArchiServiceInstaller.cs
@@ -48,14 +48,11 @@ namespace ArchiSteamFarm {
serviceInstaller.Installers.Clear();
- EventLogInstaller logInstaller = new EventLogInstaller {
- Log = SharedInfo.EventLog,
- Source = SharedInfo.EventLogSource
- };
+ EventLogInstaller logInstaller = new EventLogInstaller { Log = SharedInfo.EventLog, Source = SharedInfo.EventLogSource };
Installers.Add(serviceInstaller);
Installers.Add(serviceProcessInstaller);
Installers.Add(logInstaller);
}
}
-}
+}
\ No newline at end of file
diff --git a/ArchiSteamFarm/ArchiWebHandler.cs b/ArchiSteamFarm/ArchiWebHandler.cs
index 6c733c9fe..8f5897fce 100644
--- a/ArchiSteamFarm/ArchiWebHandler.cs
+++ b/ArchiSteamFarm/ArchiWebHandler.cs
@@ -22,29 +22,29 @@
*/
-using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
-using HtmlAgilityPack;
-using SteamKit2;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
+using System.Threading;
using System.Threading.Tasks;
using System.Xml;
-using System.Threading;
using ArchiSteamFarm.JSON;
+using HtmlAgilityPack;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using SteamKit2;
namespace ArchiSteamFarm {
internal sealed class ArchiWebHandler : IDisposable {
+ private const byte MinSessionTTL = GlobalConfig.DefaultHttpTimeout/4; // Assume session is valid for at least that amount of seconds
private const string SteamCommunityHost = "steamcommunity.com";
private const string SteamStoreHost = "store.steampowered.com";
- private const byte MinSessionTTL = GlobalConfig.DefaultHttpTimeout / 4; // Assume session is valid for at least that amount of seconds
private static string SteamCommunityURL = "https://" + SteamCommunityHost;
private static string SteamStoreURL = "https://" + SteamStoreHost;
- private static int Timeout = GlobalConfig.DefaultHttpTimeout * 1000; // This must be int type
+ private static int Timeout = GlobalConfig.DefaultHttpTimeout*1000; // This must be int type
private readonly Bot Bot;
private readonly SemaphoreSlim SessionSemaphore = new SemaphoreSlim(1);
@@ -52,108 +52,8 @@ namespace ArchiSteamFarm {
internal bool Ready { get; private set; }
- private ulong SteamID;
private DateTime LastSessionRefreshCheck = DateTime.MinValue;
-
- internal static void Init() {
- Timeout = Program.GlobalConfig.HttpTimeout * 1000;
- SteamCommunityURL = (Program.GlobalConfig.ForceHttp ? "http://" : "https://") + SteamCommunityHost;
- SteamStoreURL = (Program.GlobalConfig.ForceHttp ? "http://" : "https://") + SteamStoreHost;
- }
-
- private static uint GetAppIDFromMarketHashName(string hashName) {
- if (string.IsNullOrEmpty(hashName)) {
- ASF.ArchiLogger.LogNullError(nameof(hashName));
- return 0;
- }
-
- int index = hashName.IndexOf('-');
- if (index <= 0) {
- return 0;
- }
-
- uint appID;
- return uint.TryParse(hashName.Substring(0, index), out appID) ? appID : 0;
- }
-
- private static Steam.Item.EType GetItemType(string name) {
- if (string.IsNullOrEmpty(name)) {
- ASF.ArchiLogger.LogNullError(nameof(name));
- return Steam.Item.EType.Unknown;
- }
-
- switch (name) {
- case "Booster Pack":
- return Steam.Item.EType.BoosterPack;
- case "Coupon":
- return Steam.Item.EType.Coupon;
- case "Gift":
- return Steam.Item.EType.Gift;
- case "Steam Gems":
- return Steam.Item.EType.SteamGems;
- default:
- if (name.EndsWith("Emoticon", StringComparison.Ordinal)) {
- return Steam.Item.EType.Emoticon;
- }
-
- if (name.EndsWith("Foil Trading Card", StringComparison.Ordinal)) {
- return Steam.Item.EType.FoilTradingCard;
- }
-
- if (name.EndsWith("Profile Background", StringComparison.Ordinal)) {
- return Steam.Item.EType.ProfileBackground;
- }
-
- return name.EndsWith("Trading Card", StringComparison.Ordinal) ? Steam.Item.EType.TradingCard : Steam.Item.EType.Unknown;
- }
- }
-
- private static bool ParseItems(Dictionary> descriptions, List input, HashSet output) {
- if ((descriptions == null) || (input == null) || (input.Count == 0) || (output == null)) {
- ASF.ArchiLogger.LogNullError(nameof(descriptions) + " || " + nameof(input) + " || " + nameof(output));
- return false;
- }
-
- foreach (KeyValue item in input) {
- uint appID = item["appid"].AsUnsignedInteger();
- if (appID == 0) {
- ASF.ArchiLogger.LogNullError(nameof(appID));
- return false;
- }
-
- ulong contextID = item["contextid"].AsUnsignedLong();
- if (contextID == 0) {
- ASF.ArchiLogger.LogNullError(nameof(contextID));
- return false;
- }
-
- ulong classID = item["classid"].AsUnsignedLong();
- if (classID == 0) {
- ASF.ArchiLogger.LogNullError(nameof(classID));
- return false;
- }
-
- uint amount = item["amount"].AsUnsignedInteger();
- if (amount == 0) {
- ASF.ArchiLogger.LogNullError(nameof(amount));
- return false;
- }
-
- uint realAppID = appID;
- Steam.Item.EType type = Steam.Item.EType.Unknown;
-
- Tuple description;
- if (descriptions.TryGetValue(classID, out description)) {
- realAppID = description.Item1;
- type = description.Item2;
- }
-
- Steam.Item steamItem = new Steam.Item(appID, contextID, classID, amount, realAppID, type);
- output.Add(steamItem);
- }
-
- return true;
- }
+ private ulong SteamID;
internal ArchiWebHandler(Bot bot) {
if (bot == null) {
@@ -167,127 +67,28 @@ namespace ArchiSteamFarm {
public void Dispose() => SessionSemaphore.Dispose();
- internal void OnDisconnected() => Ready = false;
-
- internal async Task Init(ulong steamID, EUniverse universe, string webAPIUserNonce, string parentalPin) {
- if ((steamID == 0) || (universe == EUniverse.Invalid) || string.IsNullOrEmpty(webAPIUserNonce) || string.IsNullOrEmpty(parentalPin)) {
- Bot.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(universe) + " || " + nameof(webAPIUserNonce) + " || " + nameof(parentalPin));
- return false;
+ internal async Task AcceptTradeOffer(ulong tradeID) {
+ if (tradeID == 0) {
+ Bot.ArchiLogger.LogNullError(nameof(tradeID));
+ return;
}
- SteamID = steamID;
-
- string sessionID = Convert.ToBase64String(Encoding.UTF8.GetBytes(steamID.ToString()));
-
- // Generate an AES session key
- byte[] sessionKey = SteamKit2.CryptoHelper.GenerateRandomBlock(32);
-
- // RSA encrypt it with the public key for the universe we're on
- byte[] cryptedSessionKey;
- using (RSACrypto rsa = new RSACrypto(KeyDictionary.GetPublicKey(universe))) {
- cryptedSessionKey = rsa.Encrypt(sessionKey);
- }
-
- // Copy our login key
- byte[] loginKey = new byte[webAPIUserNonce.Length];
- Array.Copy(Encoding.ASCII.GetBytes(webAPIUserNonce), loginKey, webAPIUserNonce.Length);
-
- // AES encrypt the loginkey with our session key
- byte[] cryptedLoginKey = SteamKit2.CryptoHelper.SymmetricEncrypt(loginKey, sessionKey);
-
- // Do the magic
- Bot.ArchiLogger.LogGenericInfo("Logging in to ISteamUserAuth...");
-
- KeyValue authResult;
- using (dynamic iSteamUserAuth = WebAPI.GetInterface("ISteamUserAuth")) {
- iSteamUserAuth.Timeout = Timeout;
-
- try {
- authResult = iSteamUserAuth.AuthenticateUser(
- steamid: steamID,
- sessionkey: Encoding.ASCII.GetString(WebUtility.UrlEncodeToBytes(cryptedSessionKey, 0, cryptedSessionKey.Length)),
- encrypted_loginkey: Encoding.ASCII.GetString(WebUtility.UrlEncodeToBytes(cryptedLoginKey, 0, cryptedLoginKey.Length)),
- method: WebRequestMethods.Http.Post,
- secure: !Program.GlobalConfig.ForceHttp
- );
- } catch (Exception e) {
- Bot.ArchiLogger.LogGenericException(e);
- return false;
- }
- }
-
- if (authResult == null) {
- Bot.ArchiLogger.LogNullError(nameof(authResult));
- return false;
- }
-
- string steamLogin = authResult["token"].Value;
- if (string.IsNullOrEmpty(steamLogin)) {
- Bot.ArchiLogger.LogNullError(nameof(steamLogin));
- return false;
- }
-
- string steamLoginSecure = authResult["tokensecure"].Value;
- if (string.IsNullOrEmpty(steamLoginSecure)) {
- Bot.ArchiLogger.LogNullError(nameof(steamLoginSecure));
- return false;
- }
-
- WebBrowser.CookieContainer.Add(new Cookie("sessionid", sessionID, "/", "." + SteamCommunityHost));
- WebBrowser.CookieContainer.Add(new Cookie("sessionid", sessionID, "/", "." + SteamStoreHost));
-
- WebBrowser.CookieContainer.Add(new Cookie("steamLogin", steamLogin, "/", "." + SteamCommunityHost));
- WebBrowser.CookieContainer.Add(new Cookie("steamLogin", steamLogin, "/", "." + SteamStoreHost));
-
- WebBrowser.CookieContainer.Add(new Cookie("steamLoginSecure", steamLoginSecure, "/", "." + SteamCommunityHost));
- WebBrowser.CookieContainer.Add(new Cookie("steamLoginSecure", steamLoginSecure, "/", "." + SteamStoreHost));
-
- Bot.ArchiLogger.LogGenericInfo("Success!");
-
- // Unlock Steam Parental if needed
- if (!parentalPin.Equals("0")) {
- if (!await UnlockParentalAccount(parentalPin).ConfigureAwait(false)) {
- return false;
- }
- }
-
- Ready = true;
- LastSessionRefreshCheck = DateTime.Now;
- return true;
- }
-
- internal async Task> GetFamilySharingSteamIDs() {
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
- return null;
+ return;
}
- string request = SteamStoreURL + "/account/managedevices";
- HtmlDocument htmlDocument = await WebBrowser.UrlGetToHtmlDocumentRetry(request).ConfigureAwait(false);
-
- HtmlNodeCollection htmlNodes = htmlDocument?.DocumentNode.SelectNodes("(//table[@class='accountTable'])[last()]//a/@data-miniprofile");
- if (htmlNodes == null) {
- return null; // OK, no authorized steamIDs
+ string sessionID = WebBrowser.CookieContainer.GetCookieValue(SteamCommunityURL, "sessionid");
+ if (string.IsNullOrEmpty(sessionID)) {
+ Bot.ArchiLogger.LogNullError(nameof(sessionID));
+ return;
}
- HashSet result = new HashSet();
+ string referer = SteamCommunityURL + "/tradeoffer/" + tradeID;
+ string request = referer + "/accept";
- foreach (string miniProfile in htmlNodes.Select(htmlNode => htmlNode.GetAttributeValue("data-miniprofile", null))) {
- if (string.IsNullOrEmpty(miniProfile)) {
- Bot.ArchiLogger.LogNullError(nameof(miniProfile));
- return null;
- }
+ Dictionary data = new Dictionary(3) { { "sessionid", sessionID }, { "serverid", "1" }, { "tradeofferid", tradeID.ToString() } };
- uint steamID3;
- if (!uint.TryParse(miniProfile, out steamID3) || (steamID3 == 0)) {
- Bot.ArchiLogger.LogNullError(nameof(steamID3));
- return null;
- }
-
- ulong steamID = new SteamID(steamID3, EUniverse.Public, EAccountType.Individual);
- result.Add(steamID);
- }
-
- return result;
+ await WebBrowser.UrlPostRetry(request, data, referer).ConfigureAwait(false);
}
internal async Task AddFreeLicense(uint subID) {
@@ -307,180 +108,25 @@ namespace ArchiSteamFarm {
}
string request = SteamStoreURL + "/checkout/addfreelicense";
- Dictionary data = new Dictionary(3) {
- { "sessionid", sessionID },
- { "subid", subID.ToString() },
- { "action", "add_to_cart" }
- };
+ Dictionary data = new Dictionary(3) { { "sessionid", sessionID }, { "subid", subID.ToString() }, { "action", "add_to_cart" } };
HtmlDocument htmlDocument = await WebBrowser.UrlPostToHtmlDocumentRetry(request, data).ConfigureAwait(false);
return htmlDocument?.DocumentNode.SelectSingleNode("//div[@class='add_free_content_success_area']") != null;
}
- internal async Task JoinGroup(ulong groupID) {
- if (groupID == 0) {
- Bot.ArchiLogger.LogNullError(nameof(groupID));
- return false;
- }
-
- if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
- return false;
- }
-
- string sessionID = WebBrowser.CookieContainer.GetCookieValue(SteamCommunityURL, "sessionid");
- if (string.IsNullOrEmpty(sessionID)) {
- Bot.ArchiLogger.LogNullError(nameof(sessionID));
- return false;
- }
-
- string request = SteamCommunityURL + "/gid/" + groupID;
- Dictionary data = new Dictionary(2) {
- { "sessionID", sessionID },
- { "action", "join" }
- };
-
- return await WebBrowser.UrlPostRetry(request, data).ConfigureAwait(false);
- }
-
- internal async Task GetConfirmations(string deviceID, string confirmationHash, uint time) {
- if (string.IsNullOrEmpty(deviceID) || string.IsNullOrEmpty(confirmationHash) || (time == 0)) {
- Bot.ArchiLogger.LogNullError(nameof(deviceID) + " || " + nameof(confirmationHash) + " || " + nameof(time));
- return null;
- }
-
- if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
- return null;
- }
-
- string request = SteamCommunityURL + "/mobileconf/conf?l=english&p=" + deviceID + "&a=" + SteamID + "&k=" + WebUtility.UrlEncode(confirmationHash) + "&t=" + time + "&m=android&tag=conf";
- return await WebBrowser.UrlGetToHtmlDocumentRetry(request).ConfigureAwait(false);
- }
-
- internal async Task GetConfirmationDetails(string deviceID, string confirmationHash, uint time, MobileAuthenticator.Confirmation confirmation) {
- if (string.IsNullOrEmpty(deviceID) || string.IsNullOrEmpty(confirmationHash) || (time == 0) || (confirmation == null)) {
- Bot.ArchiLogger.LogNullError(nameof(deviceID) + " || " + nameof(confirmationHash) + " || " + nameof(time) + " || " + nameof(confirmation));
- return null;
- }
-
- if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
- return null;
- }
-
- string request = SteamCommunityURL + "/mobileconf/details/" + confirmation.ID + "?l=english&p=" + deviceID + "&a=" + SteamID + "&k=" + WebUtility.UrlEncode(confirmationHash) + "&t=" + time + "&m=android&tag=conf";
-
- Steam.ConfirmationDetails response = await WebBrowser.UrlGetToJsonResultRetry(request).ConfigureAwait(false);
- if ((response == null) || !response.Success) {
- return null;
- }
-
- response.Confirmation = confirmation;
- return response;
- }
-
- internal async Task HandleConfirmation(string deviceID, string confirmationHash, uint time, uint confirmationID, ulong confirmationKey, bool accept) {
- if (string.IsNullOrEmpty(deviceID) || string.IsNullOrEmpty(confirmationHash) || (time == 0) || (confirmationID == 0) || (confirmationKey == 0)) {
- Bot.ArchiLogger.LogNullError(nameof(deviceID) + " || " + nameof(confirmationHash) + " || " + nameof(time) + " || " + nameof(confirmationID) + " || " + nameof(confirmationKey));
- return null;
- }
-
- if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
- return null;
- }
-
- string request = SteamCommunityURL + "/mobileconf/ajaxop?op=" + (accept ? "allow" : "cancel") + "&p=" + deviceID + "&a=" + SteamID + "&k=" + WebUtility.UrlEncode(confirmationHash) + "&t=" + time + "&m=android&tag=conf&cid=" + confirmationID + "&ck=" + confirmationKey;
-
- Steam.ConfirmationResponse response = await WebBrowser.UrlGetToJsonResultRetry(request).ConfigureAwait(false);
- return response?.Success;
- }
-
- internal async Task HandleConfirmations(string deviceID, string confirmationHash, uint time, HashSet confirmations, bool accept) {
- if (string.IsNullOrEmpty(deviceID) || string.IsNullOrEmpty(confirmationHash) || (time == 0) || (confirmations == null) || (confirmations.Count == 0)) {
- Bot.ArchiLogger.LogNullError(nameof(deviceID) + " || " + nameof(confirmationHash) + " || " + nameof(time) + " || " + nameof(confirmations));
- return null;
- }
-
- if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
- return null;
- }
-
- string request = SteamCommunityURL + "/mobileconf/multiajaxop";
-
- List> data = new List>(7 + confirmations.Count * 2) {
- new KeyValuePair("op", accept ? "allow" : "cancel"),
- new KeyValuePair("p", deviceID),
- new KeyValuePair("a", SteamID.ToString()),
- new KeyValuePair("k", confirmationHash),
- new KeyValuePair("t", time.ToString()),
- new KeyValuePair("m", "android"),
- new KeyValuePair("tag", "conf")
- };
-
- foreach (MobileAuthenticator.Confirmation confirmation in confirmations) {
- data.Add(new KeyValuePair("cid[]", confirmation.ID.ToString()));
- data.Add(new KeyValuePair("ck[]", confirmation.Key.ToString()));
- }
-
- Steam.ConfirmationResponse response = await WebBrowser.UrlPostToJsonResultRetry(request, data).ConfigureAwait(false);
- return response?.Success;
- }
-
- internal async Task> GetOwnedGames() {
- if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
- return null;
- }
-
- string request = SteamCommunityURL + "/my/games/?xml=1";
-
- XmlDocument response = await WebBrowser.UrlGetToXMLRetry(request).ConfigureAwait(false);
-
- XmlNodeList xmlNodeList = response?.SelectNodes("gamesList/games/game");
- if ((xmlNodeList == null) || (xmlNodeList.Count == 0)) {
- return null;
- }
-
- Dictionary result = new Dictionary(xmlNodeList.Count);
- foreach (XmlNode xmlNode in xmlNodeList) {
- XmlNode appNode = xmlNode.SelectSingleNode("appID");
- if (appNode == null) {
- Bot.ArchiLogger.LogNullError(nameof(appNode));
- return null;
- }
-
- uint appID;
- if (!uint.TryParse(appNode.InnerText, out appID)) {
- Bot.ArchiLogger.LogNullError(nameof(appID));
- return null;
- }
-
- XmlNode nameNode = xmlNode.SelectSingleNode("name");
- if (nameNode == null) {
- Bot.ArchiLogger.LogNullError(nameof(nameNode));
- return null;
- }
-
- result[appID] = nameNode.InnerText;
- }
-
- return result;
- }
-
- internal Dictionary GetOwnedGames(ulong steamID) {
- if ((steamID == 0) || string.IsNullOrEmpty(Bot.BotConfig.SteamApiKey)) {
- Bot.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(Bot.BotConfig.SteamApiKey));
- return null;
+ internal void DeclineTradeOffer(ulong tradeID) {
+ if ((tradeID == 0) || string.IsNullOrEmpty(Bot.BotConfig.SteamApiKey)) {
+ Bot.ArchiLogger.LogNullError(nameof(tradeID) + " || " + nameof(Bot.BotConfig.SteamApiKey));
+ return;
}
KeyValue response = null;
for (byte i = 0; (i < WebBrowser.MaxRetries) && (response == null); i++) {
- using (dynamic iPlayerService = WebAPI.GetInterface("IPlayerService", Bot.BotConfig.SteamApiKey)) {
- iPlayerService.Timeout = Timeout;
+ using (dynamic iEconService = WebAPI.GetInterface("IEconService", Bot.BotConfig.SteamApiKey)) {
+ iEconService.Timeout = Timeout;
try {
- response = iPlayerService.GetOwnedGames(
- steamid: steamID,
- include_appinfo: 1,
- secure: !Program.GlobalConfig.ForceHttp
- );
+ response = iEconService.DeclineTradeOffer(tradeofferid: tradeID.ToString(), method: WebRequestMethods.Http.Post, secure: !Program.GlobalConfig.ForceHttp);
} catch (Exception e) {
Bot.ArchiLogger.LogGenericException(e);
}
@@ -489,116 +135,7 @@ namespace ArchiSteamFarm {
if (response == null) {
Bot.ArchiLogger.LogGenericWarning("Request failed even after " + WebBrowser.MaxRetries + " tries");
- return null;
}
-
- Dictionary result = new Dictionary(response["games"].Children.Count);
- foreach (KeyValue game in response["games"].Children) {
- uint appID = game["appid"].AsUnsignedInteger();
- if (appID == 0) {
- Bot.ArchiLogger.LogNullError(nameof(appID));
- return null;
- }
-
- result[appID] = game["name"].Value;
- }
-
- return result;
- }
-
- internal uint GetServerTime() {
- KeyValue response = null;
- for (byte i = 0; (i < WebBrowser.MaxRetries) && (response == null); i++) {
- using (dynamic iTwoFactorService = WebAPI.GetInterface("ITwoFactorService")) {
- iTwoFactorService.Timeout = Timeout;
-
- try {
- response = iTwoFactorService.QueryTime(
- method: WebRequestMethods.Http.Post,
- secure: !Program.GlobalConfig.ForceHttp
- );
- } catch (Exception e) {
- Bot.ArchiLogger.LogGenericException(e);
- }
- }
- }
-
- if (response != null) {
- return response["server_time"].AsUnsignedInteger();
- }
-
- Bot.ArchiLogger.LogGenericWarning("Request failed even after " + WebBrowser.MaxRetries + " tries");
- return 0;
- }
-
- internal async Task GetTradeHoldDuration(ulong tradeID) {
- if (tradeID == 0) {
- Bot.ArchiLogger.LogNullError(nameof(tradeID));
- return null;
- }
-
- if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
- return null;
- }
-
- string request = SteamCommunityURL + "/tradeoffer/" + tradeID + "?l=english";
-
- HtmlDocument htmlDocument = await WebBrowser.UrlGetToHtmlDocumentRetry(request).ConfigureAwait(false);
-
- HtmlNode htmlNode = htmlDocument?.DocumentNode.SelectSingleNode("//div[@class='pagecontent']/script");
- if (htmlNode == null) { // Trade can be no longer valid
- return null;
- }
-
- string text = htmlNode.InnerText;
- if (string.IsNullOrEmpty(text)) {
- Bot.ArchiLogger.LogNullError(nameof(text));
- return null;
- }
-
- int index = text.IndexOf("g_daysTheirEscrow = ", StringComparison.Ordinal);
- if (index < 0) {
- Bot.ArchiLogger.LogNullError(nameof(index));
- return null;
- }
-
- index += 20;
- text = text.Substring(index);
-
- index = text.IndexOf(';');
- if (index < 0) {
- Bot.ArchiLogger.LogNullError(nameof(index));
- return null;
- }
-
- text = text.Substring(0, index);
-
- byte holdDuration;
- if (byte.TryParse(text, out holdDuration)) {
- return holdDuration;
- }
-
- Bot.ArchiLogger.LogNullError(nameof(holdDuration));
- return null;
- }
-
- internal async Task RedeemWalletKey(string key) {
- if (string.IsNullOrEmpty(key)) {
- Bot.ArchiLogger.LogNullError(nameof(key));
- return ArchiHandler.PurchaseResponseCallback.EPurchaseResult.Unknown;
- }
-
- if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
- return ArchiHandler.PurchaseResponseCallback.EPurchaseResult.Timeout;
- }
-
- string request = SteamStoreURL + "/account/validatewalletcode";
- Dictionary data = new Dictionary(1) {
- { "wallet_code", key }
- };
-
- Steam.RedeemWalletResponse response = await WebBrowser.UrlPostToJsonResultRetry(request, data).ConfigureAwait(false);
- return response?.PurchaseResult ?? ArchiHandler.PurchaseResponseCallback.EPurchaseResult.Timeout;
}
internal HashSet GetActiveTradeOffers() {
@@ -613,12 +150,7 @@ namespace ArchiSteamFarm {
iEconService.Timeout = Timeout;
try {
- response = iEconService.GetTradeOffers(
- get_received_offers: 1,
- active_only: 1,
- get_descriptions: 1,
- secure: !Program.GlobalConfig.ForceHttp
- );
+ response = iEconService.GetTradeOffers(get_received_offers: 1, active_only: 1, get_descriptions: 1, secure: !Program.GlobalConfig.ForceHttp);
} catch (Exception e) {
Bot.ArchiLogger.LogGenericException(e);
}
@@ -711,60 +243,101 @@ namespace ArchiSteamFarm {
return result;
}
- internal async Task AcceptTradeOffer(ulong tradeID) {
- if (tradeID == 0) {
- Bot.ArchiLogger.LogNullError(nameof(tradeID));
- return;
+ internal async Task GetBadgePage(byte page) {
+ if (page == 0) {
+ Bot.ArchiLogger.LogNullError(nameof(page));
+ return null;
}
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
- return;
+ return null;
}
- string sessionID = WebBrowser.CookieContainer.GetCookieValue(SteamCommunityURL, "sessionid");
- if (string.IsNullOrEmpty(sessionID)) {
- Bot.ArchiLogger.LogNullError(nameof(sessionID));
- return;
- }
-
- string referer = SteamCommunityURL + "/tradeoffer/" + tradeID;
- string request = referer + "/accept";
-
- Dictionary data = new Dictionary(3) {
- { "sessionid", sessionID },
- { "serverid", "1" },
- { "tradeofferid", tradeID.ToString() }
- };
-
- await WebBrowser.UrlPostRetry(request, data, referer).ConfigureAwait(false);
+ string request = SteamCommunityURL + "/my/badges?l=english&p=" + page;
+ return await WebBrowser.UrlGetToHtmlDocumentRetry(request).ConfigureAwait(false);
}
- internal void DeclineTradeOffer(ulong tradeID) {
- if ((tradeID == 0) || string.IsNullOrEmpty(Bot.BotConfig.SteamApiKey)) {
- Bot.ArchiLogger.LogNullError(nameof(tradeID) + " || " + nameof(Bot.BotConfig.SteamApiKey));
- return;
+ internal async Task GetConfirmationDetails(string deviceID, string confirmationHash, uint time, MobileAuthenticator.Confirmation confirmation) {
+ if (string.IsNullOrEmpty(deviceID) || string.IsNullOrEmpty(confirmationHash) || (time == 0) || (confirmation == null)) {
+ Bot.ArchiLogger.LogNullError(nameof(deviceID) + " || " + nameof(confirmationHash) + " || " + nameof(time) + " || " + nameof(confirmation));
+ return null;
}
- KeyValue response = null;
- for (byte i = 0; (i < WebBrowser.MaxRetries) && (response == null); i++) {
- using (dynamic iEconService = WebAPI.GetInterface("IEconService", Bot.BotConfig.SteamApiKey)) {
- iEconService.Timeout = Timeout;
+ if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
+ return null;
+ }
- try {
- response = iEconService.DeclineTradeOffer(
- tradeofferid: tradeID.ToString(),
- method: WebRequestMethods.Http.Post,
- secure: !Program.GlobalConfig.ForceHttp
- );
- } catch (Exception e) {
- Bot.ArchiLogger.LogGenericException(e);
- }
+ string request = SteamCommunityURL + "/mobileconf/details/" + confirmation.ID + "?l=english&p=" + deviceID + "&a=" + SteamID + "&k=" + WebUtility.UrlEncode(confirmationHash) + "&t=" + time + "&m=android&tag=conf";
+
+ Steam.ConfirmationDetails response = await WebBrowser.UrlGetToJsonResultRetry(request).ConfigureAwait(false);
+ if ((response == null) || !response.Success) {
+ return null;
+ }
+
+ response.Confirmation = confirmation;
+ return response;
+ }
+
+ internal async Task GetConfirmations(string deviceID, string confirmationHash, uint time) {
+ if (string.IsNullOrEmpty(deviceID) || string.IsNullOrEmpty(confirmationHash) || (time == 0)) {
+ Bot.ArchiLogger.LogNullError(nameof(deviceID) + " || " + nameof(confirmationHash) + " || " + nameof(time));
+ return null;
+ }
+
+ if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
+ return null;
+ }
+
+ string request = SteamCommunityURL + "/mobileconf/conf?l=english&p=" + deviceID + "&a=" + SteamID + "&k=" + WebUtility.UrlEncode(confirmationHash) + "&t=" + time + "&m=android&tag=conf";
+ return await WebBrowser.UrlGetToHtmlDocumentRetry(request).ConfigureAwait(false);
+ }
+
+ internal async Task> GetFamilySharingSteamIDs() {
+ if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
+ return null;
+ }
+
+ string request = SteamStoreURL + "/account/managedevices";
+ HtmlDocument htmlDocument = await WebBrowser.UrlGetToHtmlDocumentRetry(request).ConfigureAwait(false);
+
+ HtmlNodeCollection htmlNodes = htmlDocument?.DocumentNode.SelectNodes("(//table[@class='accountTable'])[last()]//a/@data-miniprofile");
+ if (htmlNodes == null) {
+ return null; // OK, no authorized steamIDs
+ }
+
+ HashSet result = new HashSet();
+
+ foreach (string miniProfile in htmlNodes.Select(htmlNode => htmlNode.GetAttributeValue("data-miniprofile", null))) {
+ if (string.IsNullOrEmpty(miniProfile)) {
+ Bot.ArchiLogger.LogNullError(nameof(miniProfile));
+ return null;
}
+
+ uint steamID3;
+ if (!uint.TryParse(miniProfile, out steamID3) || (steamID3 == 0)) {
+ Bot.ArchiLogger.LogNullError(nameof(steamID3));
+ return null;
+ }
+
+ ulong steamID = new SteamID(steamID3, EUniverse.Public, EAccountType.Individual);
+ result.Add(steamID);
}
- if (response == null) {
- Bot.ArchiLogger.LogGenericWarning("Request failed even after " + WebBrowser.MaxRetries + " tries");
+ return result;
+ }
+
+ internal async Task GetGameCardsPage(ulong appID) {
+ if (appID == 0) {
+ Bot.ArchiLogger.LogNullError(nameof(appID));
+ return null;
}
+
+ if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
+ return null;
+ }
+
+ string request = SteamCommunityURL + "/my/gamecards/" + appID + "?l=english";
+ return await WebBrowser.UrlGetToHtmlDocumentRetry(request).ConfigureAwait(false);
}
internal async Task> GetMySteamInventory(bool tradable) {
@@ -883,6 +456,333 @@ namespace ArchiSteamFarm {
return result;
}
+ internal async Task> GetOwnedGames() {
+ if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
+ return null;
+ }
+
+ string request = SteamCommunityURL + "/my/games/?xml=1";
+
+ XmlDocument response = await WebBrowser.UrlGetToXMLRetry(request).ConfigureAwait(false);
+
+ XmlNodeList xmlNodeList = response?.SelectNodes("gamesList/games/game");
+ if ((xmlNodeList == null) || (xmlNodeList.Count == 0)) {
+ return null;
+ }
+
+ Dictionary result = new Dictionary(xmlNodeList.Count);
+ foreach (XmlNode xmlNode in xmlNodeList) {
+ XmlNode appNode = xmlNode.SelectSingleNode("appID");
+ if (appNode == null) {
+ Bot.ArchiLogger.LogNullError(nameof(appNode));
+ return null;
+ }
+
+ uint appID;
+ if (!uint.TryParse(appNode.InnerText, out appID)) {
+ Bot.ArchiLogger.LogNullError(nameof(appID));
+ return null;
+ }
+
+ XmlNode nameNode = xmlNode.SelectSingleNode("name");
+ if (nameNode == null) {
+ Bot.ArchiLogger.LogNullError(nameof(nameNode));
+ return null;
+ }
+
+ result[appID] = nameNode.InnerText;
+ }
+
+ return result;
+ }
+
+ internal Dictionary GetOwnedGames(ulong steamID) {
+ if ((steamID == 0) || string.IsNullOrEmpty(Bot.BotConfig.SteamApiKey)) {
+ Bot.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(Bot.BotConfig.SteamApiKey));
+ return null;
+ }
+
+ KeyValue response = null;
+ for (byte i = 0; (i < WebBrowser.MaxRetries) && (response == null); i++) {
+ using (dynamic iPlayerService = WebAPI.GetInterface("IPlayerService", Bot.BotConfig.SteamApiKey)) {
+ iPlayerService.Timeout = Timeout;
+
+ try {
+ response = iPlayerService.GetOwnedGames(steamid: steamID, include_appinfo: 1, secure: !Program.GlobalConfig.ForceHttp);
+ } catch (Exception e) {
+ Bot.ArchiLogger.LogGenericException(e);
+ }
+ }
+ }
+
+ if (response == null) {
+ Bot.ArchiLogger.LogGenericWarning("Request failed even after " + WebBrowser.MaxRetries + " tries");
+ return null;
+ }
+
+ Dictionary result = new Dictionary(response["games"].Children.Count);
+ foreach (KeyValue game in response["games"].Children) {
+ uint appID = game["appid"].AsUnsignedInteger();
+ if (appID == 0) {
+ Bot.ArchiLogger.LogNullError(nameof(appID));
+ return null;
+ }
+
+ result[appID] = game["name"].Value;
+ }
+
+ return result;
+ }
+
+ internal uint GetServerTime() {
+ KeyValue response = null;
+ for (byte i = 0; (i < WebBrowser.MaxRetries) && (response == null); i++) {
+ using (dynamic iTwoFactorService = WebAPI.GetInterface("ITwoFactorService")) {
+ iTwoFactorService.Timeout = Timeout;
+
+ try {
+ response = iTwoFactorService.QueryTime(method: WebRequestMethods.Http.Post, secure: !Program.GlobalConfig.ForceHttp);
+ } catch (Exception e) {
+ Bot.ArchiLogger.LogGenericException(e);
+ }
+ }
+ }
+
+ if (response != null) {
+ return response["server_time"].AsUnsignedInteger();
+ }
+
+ Bot.ArchiLogger.LogGenericWarning("Request failed even after " + WebBrowser.MaxRetries + " tries");
+ return 0;
+ }
+
+ internal async Task GetTradeHoldDuration(ulong tradeID) {
+ if (tradeID == 0) {
+ Bot.ArchiLogger.LogNullError(nameof(tradeID));
+ return null;
+ }
+
+ if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
+ return null;
+ }
+
+ string request = SteamCommunityURL + "/tradeoffer/" + tradeID + "?l=english";
+
+ HtmlDocument htmlDocument = await WebBrowser.UrlGetToHtmlDocumentRetry(request).ConfigureAwait(false);
+
+ HtmlNode htmlNode = htmlDocument?.DocumentNode.SelectSingleNode("//div[@class='pagecontent']/script");
+ if (htmlNode == null) { // Trade can be no longer valid
+ return null;
+ }
+
+ string text = htmlNode.InnerText;
+ if (string.IsNullOrEmpty(text)) {
+ Bot.ArchiLogger.LogNullError(nameof(text));
+ return null;
+ }
+
+ int index = text.IndexOf("g_daysTheirEscrow = ", StringComparison.Ordinal);
+ if (index < 0) {
+ Bot.ArchiLogger.LogNullError(nameof(index));
+ return null;
+ }
+
+ index += 20;
+ text = text.Substring(index);
+
+ index = text.IndexOf(';');
+ if (index < 0) {
+ Bot.ArchiLogger.LogNullError(nameof(index));
+ return null;
+ }
+
+ text = text.Substring(0, index);
+
+ byte holdDuration;
+ if (byte.TryParse(text, out holdDuration)) {
+ return holdDuration;
+ }
+
+ Bot.ArchiLogger.LogNullError(nameof(holdDuration));
+ return null;
+ }
+
+ internal async Task HandleConfirmation(string deviceID, string confirmationHash, uint time, uint confirmationID, ulong confirmationKey, bool accept) {
+ if (string.IsNullOrEmpty(deviceID) || string.IsNullOrEmpty(confirmationHash) || (time == 0) || (confirmationID == 0) || (confirmationKey == 0)) {
+ Bot.ArchiLogger.LogNullError(nameof(deviceID) + " || " + nameof(confirmationHash) + " || " + nameof(time) + " || " + nameof(confirmationID) + " || " + nameof(confirmationKey));
+ return null;
+ }
+
+ if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
+ return null;
+ }
+
+ string request = SteamCommunityURL + "/mobileconf/ajaxop?op=" + (accept ? "allow" : "cancel") + "&p=" + deviceID + "&a=" + SteamID + "&k=" + WebUtility.UrlEncode(confirmationHash) + "&t=" + time + "&m=android&tag=conf&cid=" + confirmationID + "&ck=" + confirmationKey;
+
+ Steam.ConfirmationResponse response = await WebBrowser.UrlGetToJsonResultRetry(request).ConfigureAwait(false);
+ return response?.Success;
+ }
+
+ internal async Task HandleConfirmations(string deviceID, string confirmationHash, uint time, HashSet confirmations, bool accept) {
+ if (string.IsNullOrEmpty(deviceID) || string.IsNullOrEmpty(confirmationHash) || (time == 0) || (confirmations == null) || (confirmations.Count == 0)) {
+ Bot.ArchiLogger.LogNullError(nameof(deviceID) + " || " + nameof(confirmationHash) + " || " + nameof(time) + " || " + nameof(confirmations));
+ return null;
+ }
+
+ if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
+ return null;
+ }
+
+ string request = SteamCommunityURL + "/mobileconf/multiajaxop";
+
+ List> data = new List>(7 + confirmations.Count*2) { new KeyValuePair("op", accept ? "allow" : "cancel"), new KeyValuePair("p", deviceID), new KeyValuePair("a", SteamID.ToString()), new KeyValuePair("k", confirmationHash), new KeyValuePair("t", time.ToString()), new KeyValuePair("m", "android"), new KeyValuePair("tag", "conf") };
+
+ foreach (MobileAuthenticator.Confirmation confirmation in confirmations) {
+ data.Add(new KeyValuePair("cid[]", confirmation.ID.ToString()));
+ data.Add(new KeyValuePair("ck[]", confirmation.Key.ToString()));
+ }
+
+ Steam.ConfirmationResponse response = await WebBrowser.UrlPostToJsonResultRetry(request, data).ConfigureAwait(false);
+ return response?.Success;
+ }
+
+ internal static void Init() {
+ Timeout = Program.GlobalConfig.HttpTimeout*1000;
+ SteamCommunityURL = (Program.GlobalConfig.ForceHttp ? "http://" : "https://") + SteamCommunityHost;
+ SteamStoreURL = (Program.GlobalConfig.ForceHttp ? "http://" : "https://") + SteamStoreHost;
+ }
+
+ internal async Task Init(ulong steamID, EUniverse universe, string webAPIUserNonce, string parentalPin) {
+ if ((steamID == 0) || (universe == EUniverse.Invalid) || string.IsNullOrEmpty(webAPIUserNonce) || string.IsNullOrEmpty(parentalPin)) {
+ Bot.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(universe) + " || " + nameof(webAPIUserNonce) + " || " + nameof(parentalPin));
+ return false;
+ }
+
+ SteamID = steamID;
+
+ string sessionID = Convert.ToBase64String(Encoding.UTF8.GetBytes(steamID.ToString()));
+
+ // Generate an AES session key
+ byte[] sessionKey = SteamKit2.CryptoHelper.GenerateRandomBlock(32);
+
+ // RSA encrypt it with the public key for the universe we're on
+ byte[] cryptedSessionKey;
+ using (RSACrypto rsa = new RSACrypto(KeyDictionary.GetPublicKey(universe))) {
+ cryptedSessionKey = rsa.Encrypt(sessionKey);
+ }
+
+ // Copy our login key
+ byte[] loginKey = new byte[webAPIUserNonce.Length];
+ Array.Copy(Encoding.ASCII.GetBytes(webAPIUserNonce), loginKey, webAPIUserNonce.Length);
+
+ // AES encrypt the loginkey with our session key
+ byte[] cryptedLoginKey = SteamKit2.CryptoHelper.SymmetricEncrypt(loginKey, sessionKey);
+
+ // Do the magic
+ Bot.ArchiLogger.LogGenericInfo("Logging in to ISteamUserAuth...");
+
+ KeyValue authResult;
+ using (dynamic iSteamUserAuth = WebAPI.GetInterface("ISteamUserAuth")) {
+ iSteamUserAuth.Timeout = Timeout;
+
+ try {
+ authResult = iSteamUserAuth.AuthenticateUser(steamid: steamID, sessionkey: Encoding.ASCII.GetString(WebUtility.UrlEncodeToBytes(cryptedSessionKey, 0, cryptedSessionKey.Length)), encrypted_loginkey: Encoding.ASCII.GetString(WebUtility.UrlEncodeToBytes(cryptedLoginKey, 0, cryptedLoginKey.Length)), method: WebRequestMethods.Http.Post, secure: !Program.GlobalConfig.ForceHttp);
+ } catch (Exception e) {
+ Bot.ArchiLogger.LogGenericException(e);
+ return false;
+ }
+ }
+
+ if (authResult == null) {
+ Bot.ArchiLogger.LogNullError(nameof(authResult));
+ return false;
+ }
+
+ string steamLogin = authResult["token"].Value;
+ if (string.IsNullOrEmpty(steamLogin)) {
+ Bot.ArchiLogger.LogNullError(nameof(steamLogin));
+ return false;
+ }
+
+ string steamLoginSecure = authResult["tokensecure"].Value;
+ if (string.IsNullOrEmpty(steamLoginSecure)) {
+ Bot.ArchiLogger.LogNullError(nameof(steamLoginSecure));
+ return false;
+ }
+
+ WebBrowser.CookieContainer.Add(new Cookie("sessionid", sessionID, "/", "." + SteamCommunityHost));
+ WebBrowser.CookieContainer.Add(new Cookie("sessionid", sessionID, "/", "." + SteamStoreHost));
+
+ WebBrowser.CookieContainer.Add(new Cookie("steamLogin", steamLogin, "/", "." + SteamCommunityHost));
+ WebBrowser.CookieContainer.Add(new Cookie("steamLogin", steamLogin, "/", "." + SteamStoreHost));
+
+ WebBrowser.CookieContainer.Add(new Cookie("steamLoginSecure", steamLoginSecure, "/", "." + SteamCommunityHost));
+ WebBrowser.CookieContainer.Add(new Cookie("steamLoginSecure", steamLoginSecure, "/", "." + SteamStoreHost));
+
+ Bot.ArchiLogger.LogGenericInfo("Success!");
+
+ // Unlock Steam Parental if needed
+ if (!parentalPin.Equals("0")) {
+ if (!await UnlockParentalAccount(parentalPin).ConfigureAwait(false)) {
+ return false;
+ }
+ }
+
+ Ready = true;
+ LastSessionRefreshCheck = DateTime.Now;
+ return true;
+ }
+
+ internal async Task JoinGroup(ulong groupID) {
+ if (groupID == 0) {
+ Bot.ArchiLogger.LogNullError(nameof(groupID));
+ return false;
+ }
+
+ if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
+ return false;
+ }
+
+ string sessionID = WebBrowser.CookieContainer.GetCookieValue(SteamCommunityURL, "sessionid");
+ if (string.IsNullOrEmpty(sessionID)) {
+ Bot.ArchiLogger.LogNullError(nameof(sessionID));
+ return false;
+ }
+
+ string request = SteamCommunityURL + "/gid/" + groupID;
+ Dictionary data = new Dictionary(2) { { "sessionID", sessionID }, { "action", "join" } };
+
+ return await WebBrowser.UrlPostRetry(request, data).ConfigureAwait(false);
+ }
+
+ internal async Task MarkInventory() {
+ if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
+ return false;
+ }
+
+ string request = SteamCommunityURL + "/my/inventory";
+ return await WebBrowser.UrlHeadRetry(request).ConfigureAwait(false);
+ }
+
+ internal void OnDisconnected() => Ready = false;
+
+ internal async Task RedeemWalletKey(string key) {
+ if (string.IsNullOrEmpty(key)) {
+ Bot.ArchiLogger.LogNullError(nameof(key));
+ return ArchiHandler.PurchaseResponseCallback.EPurchaseResult.Unknown;
+ }
+
+ if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
+ return ArchiHandler.PurchaseResponseCallback.EPurchaseResult.Timeout;
+ }
+
+ string request = SteamStoreURL + "/account/validatewalletcode";
+ Dictionary data = new Dictionary(1) { { "wallet_code", key } };
+
+ Steam.RedeemWalletResponse response = await WebBrowser.UrlPostToJsonResultRetry(request, data).ConfigureAwait(false);
+ return response?.PurchaseResult ?? ArchiHandler.PurchaseResponseCallback.EPurchaseResult.Timeout;
+ }
+
internal async Task SendTradeOffer(HashSet inventory, ulong partnerID, string token = null) {
if ((inventory == null) || (inventory.Count == 0) || (partnerID == 0)) {
Bot.ArchiLogger.LogNullError(nameof(inventory) + " || " + nameof(inventory.Count) + " || " + nameof(partnerID));
@@ -920,14 +820,7 @@ namespace ArchiSteamFarm {
string referer = SteamCommunityURL + "/tradeoffer/new";
string request = referer + "/send";
- foreach (Dictionary data in trades.Select(trade => new Dictionary(6) {
- { "sessionid", sessionID },
- { "serverid", "1" },
- { "partner", partnerID.ToString() },
- { "tradeoffermessage", "Sent by ASF" },
- { "json_tradeoffer", JsonConvert.SerializeObject(trade) },
- { "trade_offer_create_params", string.IsNullOrEmpty(token) ? "" : $"{{\"trade_offer_access_token\":\"{token}\"}}" }
- })) {
+ foreach (Dictionary data in trades.Select(trade => new Dictionary(6) { { "sessionid", sessionID }, { "serverid", "1" }, { "partner", partnerID.ToString() }, { "tradeoffermessage", "Sent by ASF" }, { "json_tradeoffer", JsonConvert.SerializeObject(trade) }, { "trade_offer_create_params", string.IsNullOrEmpty(token) ? "" : $"{{\"trade_offer_access_token\":\"{token}\"}}" } })) {
if (!await WebBrowser.UrlPostRetry(request, data, referer).ConfigureAwait(false)) {
return false;
}
@@ -936,41 +829,51 @@ namespace ArchiSteamFarm {
return true;
}
- internal async Task GetBadgePage(byte page) {
- if (page == 0) {
- Bot.ArchiLogger.LogNullError(nameof(page));
- return null;
+ private static uint GetAppIDFromMarketHashName(string hashName) {
+ if (string.IsNullOrEmpty(hashName)) {
+ ASF.ArchiLogger.LogNullError(nameof(hashName));
+ return 0;
}
- if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
- return null;
+ int index = hashName.IndexOf('-');
+ if (index <= 0) {
+ return 0;
}
- string request = SteamCommunityURL + "/my/badges?l=english&p=" + page;
- return await WebBrowser.UrlGetToHtmlDocumentRetry(request).ConfigureAwait(false);
+ uint appID;
+ return uint.TryParse(hashName.Substring(0, index), out appID) ? appID : 0;
}
- internal async Task GetGameCardsPage(ulong appID) {
- if (appID == 0) {
- Bot.ArchiLogger.LogNullError(nameof(appID));
- return null;
+ private static Steam.Item.EType GetItemType(string name) {
+ if (string.IsNullOrEmpty(name)) {
+ ASF.ArchiLogger.LogNullError(nameof(name));
+ return Steam.Item.EType.Unknown;
}
- if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
- return null;
+ switch (name) {
+ case "Booster Pack":
+ return Steam.Item.EType.BoosterPack;
+ case "Coupon":
+ return Steam.Item.EType.Coupon;
+ case "Gift":
+ return Steam.Item.EType.Gift;
+ case "Steam Gems":
+ return Steam.Item.EType.SteamGems;
+ default:
+ if (name.EndsWith("Emoticon", StringComparison.Ordinal)) {
+ return Steam.Item.EType.Emoticon;
+ }
+
+ if (name.EndsWith("Foil Trading Card", StringComparison.Ordinal)) {
+ return Steam.Item.EType.FoilTradingCard;
+ }
+
+ if (name.EndsWith("Profile Background", StringComparison.Ordinal)) {
+ return Steam.Item.EType.ProfileBackground;
+ }
+
+ return name.EndsWith("Trading Card", StringComparison.Ordinal) ? Steam.Item.EType.TradingCard : Steam.Item.EType.Unknown;
}
-
- string request = SteamCommunityURL + "/my/gamecards/" + appID + "?l=english";
- return await WebBrowser.UrlGetToHtmlDocumentRetry(request).ConfigureAwait(false);
- }
-
- internal async Task MarkInventory() {
- if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
- return false;
- }
-
- string request = SteamCommunityURL + "/my/inventory";
- return await WebBrowser.UrlHeadRetry(request).ConfigureAwait(false);
}
private async Task IsLoggedIn() {
@@ -982,6 +885,53 @@ namespace ArchiSteamFarm {
return !uri?.AbsolutePath.StartsWith("/login", StringComparison.Ordinal);
}
+ private static bool ParseItems(Dictionary> descriptions, List input, HashSet output) {
+ if ((descriptions == null) || (input == null) || (input.Count == 0) || (output == null)) {
+ ASF.ArchiLogger.LogNullError(nameof(descriptions) + " || " + nameof(input) + " || " + nameof(output));
+ return false;
+ }
+
+ foreach (KeyValue item in input) {
+ uint appID = item["appid"].AsUnsignedInteger();
+ if (appID == 0) {
+ ASF.ArchiLogger.LogNullError(nameof(appID));
+ return false;
+ }
+
+ ulong contextID = item["contextid"].AsUnsignedLong();
+ if (contextID == 0) {
+ ASF.ArchiLogger.LogNullError(nameof(contextID));
+ return false;
+ }
+
+ ulong classID = item["classid"].AsUnsignedLong();
+ if (classID == 0) {
+ ASF.ArchiLogger.LogNullError(nameof(classID));
+ return false;
+ }
+
+ uint amount = item["amount"].AsUnsignedInteger();
+ if (amount == 0) {
+ ASF.ArchiLogger.LogNullError(nameof(amount));
+ return false;
+ }
+
+ uint realAppID = appID;
+ Steam.Item.EType type = Steam.Item.EType.Unknown;
+
+ Tuple description;
+ if (descriptions.TryGetValue(classID, out description)) {
+ realAppID = description.Item1;
+ type = description.Item2;
+ }
+
+ Steam.Item steamItem = new Steam.Item(appID, contextID, classID, amount, realAppID, type);
+ output.Add(steamItem);
+ }
+
+ return true;
+ }
+
private async Task RefreshSessionIfNeeded() {
if (DateTime.Now.Subtract(LastSessionRefreshCheck).TotalSeconds < MinSessionTTL) {
return true;
@@ -1016,9 +966,7 @@ namespace ArchiSteamFarm {
Bot.ArchiLogger.LogGenericInfo("Unlocking parental account...");
string request = SteamCommunityURL + "/parental/ajaxunlock";
- Dictionary data = new Dictionary(1) {
- { "pin", parentalPin }
- };
+ Dictionary data = new Dictionary(1) { { "pin", parentalPin } };
bool result = await WebBrowser.UrlPostRetry(request, data, SteamCommunityURL).ConfigureAwait(false);
if (!result) {
@@ -1030,4 +978,4 @@ namespace ArchiSteamFarm {
return true;
}
}
-}
+}
\ No newline at end of file
diff --git a/ArchiSteamFarm/Bot.cs b/ArchiSteamFarm/Bot.cs
index 389af288b..876f1b36c 100755
--- a/ArchiSteamFarm/Bot.cs
+++ b/ArchiSteamFarm/Bot.cs
@@ -22,9 +22,6 @@
*/
-using Newtonsoft.Json;
-using SteamKit2;
-using SteamKit2.Internal;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
@@ -32,26 +29,18 @@ using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
-using System.Threading;
-using System.Threading.Tasks;
using System.Text;
using System.Text.RegularExpressions;
+using System.Threading;
+using System.Threading.Tasks;
using ArchiSteamFarm.JSON;
+using Newtonsoft.Json;
+using SteamKit2;
using SteamKit2.Discovery;
+using SteamKit2.Internal;
namespace ArchiSteamFarm {
internal sealed class Bot : IDisposable {
- [Flags]
- private enum ERedeemFlags : byte {
- None = 0,
- Validate = 1,
- ForceForwarding = 2,
- SkipForwarding = 4,
- ForceDistribution = 8,
- SkipDistribution = 16,
- SkipInitial = 32
- }
-
private const ushort CallbackSleep = 500; // In miliseconds
private const byte FamilySharingInactivityMinutes = 5;
private const uint LoginID = 0; // This must be the same for all ASF bots and all ASF processes
@@ -62,104 +51,52 @@ namespace ArchiSteamFarm {
private static readonly SemaphoreSlim GiftsSemaphore = new SemaphoreSlim(1);
private static readonly SemaphoreSlim LoginSemaphore = new SemaphoreSlim(1);
- internal readonly string BotName;
internal readonly ArchiHandler ArchiHandler;
internal readonly ArchiLogger ArchiLogger;
internal readonly ArchiWebHandler ArchiWebHandler;
-
- private readonly string SentryFile;
- private readonly BotDatabase BotDatabase;
- private readonly CallbackManager CallbackManager;
-
- [JsonProperty]
- private readonly CardsFarmer CardsFarmer;
-
- private readonly ConcurrentHashSet HandledGifts = new ConcurrentHashSet();
- private readonly ConcurrentHashSet SteamFamilySharingIDs = new ConcurrentHashSet();
- private readonly ConcurrentHashSet OwnedPackageIDs = new ConcurrentHashSet();
- private readonly SemaphoreSlim CallbackSemaphore = new SemaphoreSlim(1);
- private readonly SemaphoreSlim InitializationSemaphore = new SemaphoreSlim(1);
- private readonly SteamApps SteamApps;
- private readonly SteamClient SteamClient;
- private readonly SteamFriends SteamFriends;
- private readonly SteamUser SteamUser;
- private readonly Timer HeartBeatTimer;
- private readonly Trading Trading;
-
- [JsonProperty]
- internal bool KeepRunning { get; private set; }
-
- internal bool IsLimitedUser { get; private set; }
- internal BotConfig BotConfig { get; private set; }
+ internal readonly string BotName;
internal bool HasMobileAuthenticator => BotDatabase.MobileAuthenticator != null;
internal bool IsConnectedAndLoggedOn => SteamClient.IsConnected && (SteamClient.SteamID != null);
internal bool IsPlayingPossible => !PlayingBlocked && (LibraryLockedBySteamID == 0);
internal ulong SteamID => SteamClient.SteamID;
- private bool FirstTradeSent, PlayingBlocked, SkipFirstShutdown;
- private byte HeartBeatFailures;
+ private readonly BotDatabase BotDatabase;
+ private readonly CallbackManager CallbackManager;
+ private readonly SemaphoreSlim CallbackSemaphore = new SemaphoreSlim(1);
+
+ [JsonProperty]
+ private readonly CardsFarmer CardsFarmer;
+
+ private readonly ConcurrentHashSet HandledGifts = new ConcurrentHashSet();
+ private readonly Timer HeartBeatTimer;
+ private readonly SemaphoreSlim InitializationSemaphore = new SemaphoreSlim(1);
+ private readonly ConcurrentHashSet OwnedPackageIDs = new ConcurrentHashSet();
+
+ private readonly string SentryFile;
+ private readonly SteamApps SteamApps;
+ private readonly SteamClient SteamClient;
+ private readonly ConcurrentHashSet SteamFamilySharingIDs = new ConcurrentHashSet();
+ private readonly SteamFriends SteamFriends;
+ private readonly SteamUser SteamUser;
+ private readonly Trading Trading;
+
+ internal BotConfig BotConfig { get; private set; }
+ internal bool IsLimitedUser { get; private set; }
+
+ [JsonProperty]
+ internal bool KeepRunning { get; private set; }
+
+ private Timer AcceptConfirmationsTimer;
private string AuthCode, TwoFactorCode;
- private ulong LibraryLockedBySteamID;
+ private Timer FamilySharingInactivityTimer;
+ private bool FirstTradeSent;
+ private byte HeartBeatFailures;
private EResult LastLogOnResult;
- private Timer AcceptConfirmationsTimer, FamilySharingInactivityTimer, SendItemsTimer;
-
- internal static string GetAPIStatus() {
- var response = new {
- Bots
- };
-
- try {
- return JsonConvert.SerializeObject(response);
- } catch (JsonException e) {
- ASF.ArchiLogger.LogGenericException(e);
- return null;
- }
- }
-
- internal static void InitializeCMs(uint cellID, IServerListProvider serverListProvider) {
- if (serverListProvider == null) {
- ASF.ArchiLogger.LogNullError(nameof(serverListProvider));
- return;
- }
-
- CMClient.Servers.CellID = cellID;
- CMClient.Servers.ServerListProvider = serverListProvider;
- }
-
- private static bool IsOwner(ulong steamID) {
- if (steamID != 0) {
- return (steamID == Program.GlobalConfig.SteamOwnerID) || (Debugging.IsDebugBuild && (steamID == SharedInfo.ArchiSteamID));
- }
-
- ASF.ArchiLogger.LogNullError(nameof(steamID));
- return false;
- }
-
- private static bool IsValidCdKey(string key) {
- if (!string.IsNullOrEmpty(key)) {
- return Regex.IsMatch(key, @"^[0-9A-Z]{4,7}-[0-9A-Z]{4,7}-[0-9A-Z]{4,7}(?:(?:-[0-9A-Z]{4,7})?(?:-[0-9A-Z]{4,7}))?$", RegexOptions.IgnoreCase);
- }
-
- ASF.ArchiLogger.LogNullError(nameof(key));
- return false;
- }
-
- private static async Task LimitGiftsRequestsAsync() {
- await GiftsSemaphore.WaitAsync().ConfigureAwait(false);
- Task.Run(async () => {
- await Task.Delay(Program.GlobalConfig.GiftsLimiterDelay * 1000).ConfigureAwait(false);
- GiftsSemaphore.Release();
- }).Forget();
- }
-
- private static async Task LimitLoginRequestsAsync() {
- await LoginSemaphore.WaitAsync().ConfigureAwait(false);
- Task.Run(async () => {
- await Task.Delay(Program.GlobalConfig.LoginLimiterDelay * 1000).ConfigureAwait(false);
- LoginSemaphore.Release();
- }).Forget();
- }
+ private ulong LibraryLockedBySteamID;
+ private bool PlayingBlocked;
+ private Timer SendItemsTimer;
+ private bool SkipFirstShutdown;
internal Bot(string botName) {
if (string.IsNullOrEmpty(botName)) {
@@ -262,97 +199,13 @@ namespace ArchiSteamFarm {
Trading = new Trading(this);
- HeartBeatTimer = new Timer(
- async e => await HeartBeat().ConfigureAwait(false),
- null,
- TimeSpan.FromMinutes(1) + TimeSpan.FromMinutes(0.2 * Bots.Count), // Delay
+ 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();
}
- internal async Task OnNewConfigLoaded(ASF.BotConfigEventArgs args) {
- if (args == null) {
- ArchiLogger.LogNullError(nameof(args));
- return;
- }
-
- if (args.BotConfig == null) {
- Destroy();
- return;
- }
-
- if (args.BotConfig == BotConfig) {
- return;
- }
-
- await InitializationSemaphore.WaitAsync().ConfigureAwait(false);
-
- try {
- if (args.BotConfig == BotConfig) {
- return;
- }
-
- Stop(false);
- BotConfig = args.BotConfig;
-
- CardsFarmer.SetInitialState(BotConfig.Paused);
-
- if (BotConfig.AcceptConfirmationsPeriod > 0) {
- TimeSpan delay = TimeSpan.FromMinutes(BotConfig.AcceptConfirmationsPeriod) + TimeSpan.FromMinutes(0.2 * Bots.Count);
- TimeSpan period = TimeSpan.FromMinutes(BotConfig.AcceptConfirmationsPeriod);
-
- if (AcceptConfirmationsTimer == null) {
- AcceptConfirmationsTimer = new Timer(
- async e => await AcceptConfirmations(true).ConfigureAwait(false),
- null,
- delay, // Delay
- period // Period
- );
- } else {
- AcceptConfirmationsTimer.Change(delay, period);
- }
- } else {
- AcceptConfirmationsTimer?.Change(Timeout.Infinite, Timeout.Infinite);
- AcceptConfirmationsTimer?.Dispose();
- }
-
- if ((BotConfig.SendTradePeriod > 0) && (BotConfig.SteamMasterID != 0)) {
- TimeSpan delay = TimeSpan.FromHours(BotConfig.SendTradePeriod) + TimeSpan.FromMinutes(Bots.Count);
- TimeSpan period = TimeSpan.FromHours(BotConfig.SendTradePeriod);
-
- if (SendItemsTimer == null) {
- SendItemsTimer = new Timer(
- async e => await ResponseLoot(BotConfig.SteamMasterID).ConfigureAwait(false),
- null,
- delay, // Delay
- period // Period
- );
- } else {
- SendItemsTimer.Change(delay, period);
- }
- } else {
- SendItemsTimer?.Change(Timeout.Infinite, Timeout.Infinite);
- SendItemsTimer?.Dispose();
- }
-
- await Initialize().ConfigureAwait(false);
- } finally {
- InitializationSemaphore.Release();
- }
- }
-
- private async Task Initialize() {
- if (!BotConfig.Enabled) {
- ArchiLogger.LogGenericInfo("Not starting this instance because it's disabled in config file");
- return;
- }
-
- // Start
- await Start().ConfigureAwait(false);
- }
-
public void Dispose() {
// Those are objects that are always being created if constructor doesn't throw exception
ArchiWebHandler.Dispose();
@@ -400,10 +253,7 @@ namespace ArchiSteamFarm {
Steam.ConfirmationDetails[] detailsResults = await Task.WhenAll(confirmations.Select(BotDatabase.MobileAuthenticator.GetConfirmationDetails)).ConfigureAwait(false);
- HashSet ignoredConfirmations = new HashSet(detailsResults.Where(details => (details != null) && (
- ((acceptedSteamID != 0) && (details.OtherSteamID64 != 0) && (acceptedSteamID != details.OtherSteamID64)) ||
- ((acceptedTradeIDs != null) && (details.TradeOfferID != 0) && !acceptedTradeIDs.Contains(details.TradeOfferID))
- )).Select(details => details.Confirmation));
+ HashSet ignoredConfirmations = new HashSet(detailsResults.Where(details => (details != null) && (((acceptedSteamID != 0) && (details.OtherSteamID64 != 0) && (acceptedSteamID != details.OtherSteamID64)) || ((acceptedTradeIDs != null) && (details.TradeOfferID != 0) && !acceptedTradeIDs.Contains(details.TradeOfferID)))).Select(details => details.Confirmation));
if (ignoredConfirmations.Count > 0) {
confirmations.ExceptWith(ignoredConfirmations);
@@ -418,6 +268,119 @@ namespace ArchiSteamFarm {
}
}
+ internal static string GetAPIStatus() {
+ var response = new { Bots };
+
+ try {
+ return JsonConvert.SerializeObject(response);
+ } catch (JsonException e) {
+ ASF.ArchiLogger.LogGenericException(e);
+ return null;
+ }
+ }
+
+ internal static void InitializeCMs(uint cellID, IServerListProvider serverListProvider) {
+ if (serverListProvider == null) {
+ ASF.ArchiLogger.LogNullError(nameof(serverListProvider));
+ return;
+ }
+
+ CMClient.Servers.CellID = cellID;
+ CMClient.Servers.ServerListProvider = serverListProvider;
+ }
+
+ internal async Task LootIfNeeded() {
+ if (!BotConfig.SendOnFarmingFinished || (BotConfig.SteamMasterID == 0) || !IsConnectedAndLoggedOn || (BotConfig.SteamMasterID == SteamClient.SteamID)) {
+ return;
+ }
+
+ await ResponseLoot(BotConfig.SteamMasterID).ConfigureAwait(false);
+ }
+
+ internal async Task OnFarmingFinished(bool farmedSomething) {
+ OnFarmingStopped();
+
+ if (farmedSomething || !FirstTradeSent) {
+ FirstTradeSent = true;
+ await LootIfNeeded().ConfigureAwait(false);
+ }
+
+ if (BotConfig.ShutdownOnFarmingFinished) {
+ if (SkipFirstShutdown) {
+ SkipFirstShutdown = false;
+ } else {
+ Stop();
+ }
+ }
+ }
+
+ internal void OnFarmingStopped() => ResetGamesPlayed();
+
+ internal async Task OnNewConfigLoaded(ASF.BotConfigEventArgs args) {
+ if (args == null) {
+ ArchiLogger.LogNullError(nameof(args));
+ return;
+ }
+
+ if (args.BotConfig == null) {
+ Destroy();
+ return;
+ }
+
+ if (args.BotConfig == BotConfig) {
+ return;
+ }
+
+ await InitializationSemaphore.WaitAsync().ConfigureAwait(false);
+
+ try {
+ if (args.BotConfig == BotConfig) {
+ return;
+ }
+
+ Stop(false);
+ BotConfig = args.BotConfig;
+
+ CardsFarmer.SetInitialState(BotConfig.Paused);
+
+ if (BotConfig.AcceptConfirmationsPeriod > 0) {
+ TimeSpan delay = TimeSpan.FromMinutes(BotConfig.AcceptConfirmationsPeriod) + TimeSpan.FromMinutes(0.2*Bots.Count);
+ TimeSpan period = TimeSpan.FromMinutes(BotConfig.AcceptConfirmationsPeriod);
+
+ if (AcceptConfirmationsTimer == null) {
+ AcceptConfirmationsTimer = new Timer(async e => await AcceptConfirmations(true).ConfigureAwait(false), null, delay, // Delay
+ period // Period
+ );
+ } else {
+ AcceptConfirmationsTimer.Change(delay, period);
+ }
+ } else {
+ AcceptConfirmationsTimer?.Change(Timeout.Infinite, Timeout.Infinite);
+ AcceptConfirmationsTimer?.Dispose();
+ }
+
+ if ((BotConfig.SendTradePeriod > 0) && (BotConfig.SteamMasterID != 0)) {
+ TimeSpan delay = TimeSpan.FromHours(BotConfig.SendTradePeriod) + TimeSpan.FromMinutes(Bots.Count);
+ TimeSpan period = TimeSpan.FromHours(BotConfig.SendTradePeriod);
+
+ if (SendItemsTimer == null) {
+ SendItemsTimer = new Timer(async e => await ResponseLoot(BotConfig.SteamMasterID).ConfigureAwait(false), null, delay, // Delay
+ period // Period
+ );
+ } else {
+ SendItemsTimer.Change(delay, period);
+ }
+ } else {
+ SendItemsTimer?.Change(Timeout.Infinite, Timeout.Infinite);
+ SendItemsTimer?.Dispose();
+ }
+
+ await Initialize().ConfigureAwait(false);
+ } finally {
+ InitializationSemaphore.Release();
+ }
+ }
+
internal async Task RefreshSession() {
if (!IsConnectedAndLoggedOn) {
return false;
@@ -446,50 +409,6 @@ namespace ArchiSteamFarm {
return false;
}
- internal void Stop(bool withShutdownEvent = true) {
- if (!KeepRunning) {
- return;
- }
-
- ArchiLogger.LogGenericInfo("Stopping...");
- KeepRunning = false;
-
- if (SteamClient.IsConnected) {
- Disconnect();
- }
-
- if (withShutdownEvent) {
- Events.OnBotShutdown();
- }
- }
-
- internal async Task LootIfNeeded() {
- if (!BotConfig.SendOnFarmingFinished || (BotConfig.SteamMasterID == 0) || !IsConnectedAndLoggedOn || (BotConfig.SteamMasterID == SteamClient.SteamID)) {
- return;
- }
-
- await ResponseLoot(BotConfig.SteamMasterID).ConfigureAwait(false);
- }
-
- internal void OnFarmingStopped() => ResetGamesPlayed();
-
- internal async Task OnFarmingFinished(bool farmedSomething) {
- OnFarmingStopped();
-
- if (farmedSomething || !FirstTradeSent) {
- FirstTradeSent = true;
- await LootIfNeeded().ConfigureAwait(false);
- }
-
- if (BotConfig.ShutdownOnFarmingFinished) {
- if (SkipFirstShutdown) {
- SkipFirstShutdown = false;
- } else {
- Stop();
- }
- }
- }
-
internal async Task Response(ulong steamID, string message) {
if ((steamID == 0) || string.IsNullOrEmpty(message)) {
ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(message));
@@ -625,41 +544,41 @@ namespace ArchiSteamFarm {
}
}
- private void Destroy(bool force = false) {
- if (!force) {
- Stop();
- } else {
- // Stop() will most likely block due to fuckup, don't wait for it
- Task.Run(() => Stop()).Forget();
- }
-
- Bot ignored;
- Bots.TryRemove(BotName, out ignored);
- }
-
- private async Task HeartBeat() {
- if (!IsConnectedAndLoggedOn || (HeartBeatFailures == byte.MaxValue)) {
+ internal void Stop(bool withShutdownEvent = true) {
+ if (!KeepRunning) {
return;
}
- try {
- await SteamApps.PICSGetProductInfo(0, null);
- } catch {
- if (!IsConnectedAndLoggedOn || (HeartBeatFailures == byte.MaxValue)) {
- return;
- }
+ ArchiLogger.LogGenericInfo("Stopping...");
+ KeepRunning = false;
- if (++HeartBeatFailures >= 15) {
- HeartBeatFailures = byte.MaxValue;
- ArchiLogger.LogGenericError("HeartBeat failed to disconnect the client, abandoning this bot instance!");
- Destroy(true);
- new Bot(BotName).Forget();
- return;
- }
-
- ArchiLogger.LogGenericWarning("Connection to Steam Network lost, reconnecting...");
- Connect(true).Forget();
+ if (SteamClient.IsConnected) {
+ Disconnect();
}
+
+ if (withShutdownEvent) {
+ Events.OnBotShutdown();
+ }
+ }
+
+ private void CheckFamilySharingInactivity() {
+ if (!IsPlayingPossible) {
+ return;
+ }
+
+ ArchiLogger.LogGenericInfo("Shared library has not been launched in given time period, farming process resumed!");
+ CardsFarmer.Resume(false);
+ }
+
+ private void CheckOccupationStatus() {
+ if (!IsPlayingPossible) {
+ ArchiLogger.LogGenericInfo("Account is currently being used, ASF will resume farming when it's free...");
+ StopFamilySharingInactivityTimer();
+ return;
+ }
+
+ ArchiLogger.LogGenericInfo("Account is no longer occupied, farming process resumed!");
+ CardsFarmer.Resume(false);
}
private async Task Connect(bool force = false) {
@@ -686,81 +605,78 @@ namespace ArchiSteamFarm {
}
}
+ private void Destroy(bool force = false) {
+ if (!force) {
+ Stop();
+ } else {
+ // Stop() will most likely block due to fuckup, don't wait for it
+ Task.Run(() => Stop()).Forget();
+ }
+
+ Bot ignored;
+ Bots.TryRemove(BotName, out ignored);
+ }
+
private void Disconnect() {
lock (SteamClient) {
SteamClient.Disconnect();
}
}
- private async Task Start() {
- if (!KeepRunning) {
- KeepRunning = true;
- Task.Run(() => HandleCallbacks()).Forget();
- ArchiLogger.LogGenericInfo("Starting...");
- }
+ private void HandleCallbacks() {
+ TimeSpan timeSpan = TimeSpan.FromMilliseconds(CallbackSleep);
+ while (KeepRunning || SteamClient.IsConnected) {
+ if (!CallbackSemaphore.Wait(0)) {
+ return;
+ }
- await Connect().ConfigureAwait(false);
+ try {
+ CallbackManager.RunWaitCallbacks(timeSpan);
+ } catch (Exception e) {
+ ArchiLogger.LogGenericException(e);
+ } finally {
+ CallbackSemaphore.Release();
+ }
+ }
}
- private bool IsMaster(ulong steamID) {
- if (steamID != 0) {
- return (steamID == BotConfig.SteamMasterID) || IsOwner(steamID);
- }
-
- ArchiLogger.LogNullError(nameof(steamID));
- return false;
- }
-
- private void CheckOccupationStatus() {
- if (!IsPlayingPossible) {
- ArchiLogger.LogGenericInfo("Account is currently being used, ASF will resume farming when it's free...");
- StopFamilySharingInactivityTimer();
+ private async Task HandleMessage(ulong chatID, ulong steamID, string message) {
+ if ((chatID == 0) || (steamID == 0) || string.IsNullOrEmpty(message)) {
+ ArchiLogger.LogNullError(nameof(chatID) + " || " + nameof(steamID) + " || " + nameof(message));
return;
}
- ArchiLogger.LogGenericInfo("Account is no longer occupied, farming process resumed!");
- CardsFarmer.Resume(false);
- }
-
- private void CheckFamilySharingInactivity() {
- if (!IsPlayingPossible) {
+ string response = await Response(steamID, message).ConfigureAwait(false);
+ if (string.IsNullOrEmpty(response)) { // We respond with null when user is not authorized (and similar)
return;
}
- ArchiLogger.LogGenericInfo("Shared library has not been launched in given time period, farming process resumed!");
- CardsFarmer.Resume(false);
+ SendMessage(chatID, response);
}
- private void StartFamilySharingInactivityTimer() {
- if (FamilySharingInactivityTimer != null) {
+ private async Task HeartBeat() {
+ if (!IsConnectedAndLoggedOn || (HeartBeatFailures == byte.MaxValue)) {
return;
}
- FamilySharingInactivityTimer = new Timer(
- e => CheckFamilySharingInactivity(),
- null,
- TimeSpan.FromMinutes(FamilySharingInactivityMinutes), // Delay
- Timeout.InfiniteTimeSpan // Period
- );
- }
+ try {
+ await SteamApps.PICSGetProductInfo(0, null);
+ } catch {
+ if (!IsConnectedAndLoggedOn || (HeartBeatFailures == byte.MaxValue)) {
+ return;
+ }
- private void StopFamilySharingInactivityTimer() {
- if (FamilySharingInactivityTimer == null) {
- return;
+ if (++HeartBeatFailures >= 15) {
+ HeartBeatFailures = byte.MaxValue;
+ ArchiLogger.LogGenericError("HeartBeat failed to disconnect the client, abandoning this bot instance!");
+ Destroy(true);
+ new Bot(BotName).Forget();
+ return;
+ }
+
+ ArchiLogger.LogGenericWarning("Connection to Steam Network lost, reconnecting...");
+ Connect(true).Forget();
}
-
- FamilySharingInactivityTimer.Change(Timeout.Infinite, Timeout.Infinite);
- FamilySharingInactivityTimer.Dispose();
- FamilySharingInactivityTimer = null;
- }
-
- private async Task InitializeFamilySharing() {
- HashSet steamIDs = await ArchiWebHandler.GetFamilySharingSteamIDs().ConfigureAwait(false);
- if ((steamIDs == null) || (steamIDs.Count == 0)) {
- return;
- }
-
- SteamFamilySharingIDs.ReplaceIfNeededWith(steamIDs);
}
private void ImportAuthenticator(string maFilePath) {
@@ -800,284 +716,668 @@ namespace ArchiSteamFarm {
ArchiLogger.LogGenericInfo("Successfully finished importing mobile authenticator!");
}
- private string ResponsePassword(ulong steamID) {
- if (steamID == 0) {
- ArchiLogger.LogNullError(nameof(steamID));
- return null;
+ private async Task Initialize() {
+ if (!BotConfig.Enabled) {
+ ArchiLogger.LogGenericInfo("Not starting this instance because it's disabled in config file");
+ return;
}
- if (!IsMaster(steamID)) {
- return null;
- }
-
- if (string.IsNullOrEmpty(BotConfig.SteamPassword)) {
- return "Can't encrypt null password!";
- }
-
- return Environment.NewLine +
- "[" + CryptoHelper.ECryptoMethod.AES + "] password: " + CryptoHelper.Encrypt(CryptoHelper.ECryptoMethod.AES, BotConfig.SteamPassword) + Environment.NewLine +
- "[" + CryptoHelper.ECryptoMethod.ProtectedDataForCurrentUser + "] password: " + CryptoHelper.Encrypt(CryptoHelper.ECryptoMethod.ProtectedDataForCurrentUser, BotConfig.SteamPassword);
+ // Start
+ await Start().ConfigureAwait(false);
}
- private static string ResponsePassword(ulong steamID, string botName) {
- if ((steamID == 0) || string.IsNullOrEmpty(botName)) {
- ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botName));
- return null;
+ private async Task InitializeFamilySharing() {
+ HashSet steamIDs = await ArchiWebHandler.GetFamilySharingSteamIDs().ConfigureAwait(false);
+ if ((steamIDs == null) || (steamIDs.Count == 0)) {
+ return;
}
- Bot bot;
- if (Bots.TryGetValue(botName, out bot)) {
- return bot.ResponsePassword(steamID);
- }
-
- if (IsOwner(steamID)) {
- return "Couldn't find any bot named " + botName + "!";
- }
-
- return null;
+ SteamFamilySharingIDs.ReplaceIfNeededWith(steamIDs);
}
- private async Task ResponsePause(ulong steamID, bool sticky) {
- if (steamID == 0) {
- ArchiLogger.LogNullError(nameof(steamID));
- return null;
+ private bool InitializeLoginAndPassword(bool requiresPassword) {
+ if (string.IsNullOrEmpty(BotConfig.SteamLogin)) {
+ BotConfig.SteamLogin = Program.GetUserInput(ASF.EUserInputType.Login, BotName);
+ if (string.IsNullOrEmpty(BotConfig.SteamLogin)) {
+ return false;
+ }
}
- if (!IsMaster(steamID) && !SteamFamilySharingIDs.Contains(steamID)) {
- return null;
+ if (!string.IsNullOrEmpty(BotConfig.SteamPassword) || (!requiresPassword && !string.IsNullOrEmpty(BotDatabase.LoginKey))) {
+ return true;
}
- if (!IsConnectedAndLoggedOn) {
- return "This bot instance is not connected!";
- }
-
- if (CardsFarmer.Paused) {
- return "Automatic farming is paused already!";
- }
-
- await CardsFarmer.Pause(sticky).ConfigureAwait(false);
-
- if (!SteamFamilySharingIDs.Contains(steamID)) {
- return "Automatic farming is now paused!";
- }
-
- StartFamilySharingInactivityTimer();
- return "Automatic farming is now paused! You have " + FamilySharingInactivityMinutes + " minutes to start a game.";
+ BotConfig.SteamPassword = Program.GetUserInput(ASF.EUserInputType.Password, BotName);
+ return !string.IsNullOrEmpty(BotConfig.SteamPassword);
}
- private static async Task ResponsePause(ulong steamID, string botName, bool sticky) {
- if ((steamID == 0) || string.IsNullOrEmpty(botName)) {
- ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botName));
- return null;
+ private bool IsMaster(ulong steamID) {
+ if (steamID != 0) {
+ return (steamID == BotConfig.SteamMasterID) || IsOwner(steamID);
}
- Bot bot;
- if (Bots.TryGetValue(botName, out bot)) {
- return await bot.ResponsePause(steamID, sticky).ConfigureAwait(false);
- }
-
- if (IsOwner(steamID)) {
- return "Couldn't find any bot named " + botName + "!";
- }
-
- return null;
+ ArchiLogger.LogNullError(nameof(steamID));
+ return false;
}
- private string ResponseResume(ulong steamID) {
- if (steamID == 0) {
- ArchiLogger.LogNullError(nameof(steamID));
- return null;
+ private static bool IsOwner(ulong steamID) {
+ if (steamID != 0) {
+ return (steamID == Program.GlobalConfig.SteamOwnerID) || (Debugging.IsDebugBuild && (steamID == SharedInfo.ArchiSteamID));
}
- if (!IsMaster(steamID) && !SteamFamilySharingIDs.Contains(steamID)) {
- return null;
- }
-
- if (!IsConnectedAndLoggedOn) {
- return "This bot instance is not connected!";
- }
-
- if (!CardsFarmer.Paused) {
- return "Automatic farming is resumed already!";
- }
-
- StopFamilySharingInactivityTimer();
- CardsFarmer.Resume(true);
- return "Automatic farming is now resumed!";
+ ASF.ArchiLogger.LogNullError(nameof(steamID));
+ return false;
}
- private static string ResponseResume(ulong steamID, string botName) {
- if ((steamID == 0) || string.IsNullOrEmpty(botName)) {
- ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botName));
- return null;
+ private static bool IsValidCdKey(string key) {
+ if (!string.IsNullOrEmpty(key)) {
+ return Regex.IsMatch(key, @"^[0-9A-Z]{4,7}-[0-9A-Z]{4,7}-[0-9A-Z]{4,7}(?:(?:-[0-9A-Z]{4,7})?(?:-[0-9A-Z]{4,7}))?$", RegexOptions.IgnoreCase);
}
- Bot bot;
- if (Bots.TryGetValue(botName, out bot)) {
- return bot.ResponseResume(steamID);
- }
-
- if (IsOwner(steamID)) {
- return "Couldn't find any bot named " + botName + "!";
- }
-
- return null;
+ ASF.ArchiLogger.LogNullError(nameof(key));
+ return false;
}
- private string ResponseStatus(ulong steamID) {
- if (steamID == 0) {
- ArchiLogger.LogNullError(nameof(steamID));
- return null;
+ private void JoinMasterChat() {
+ if (!IsConnectedAndLoggedOn || (BotConfig.SteamMasterClanID == 0)) {
+ return;
}
- if (!IsMaster(steamID)) {
- return null;
+ SteamFriends.JoinChat(BotConfig.SteamMasterClanID);
+ }
+
+ private static async Task LimitGiftsRequestsAsync() {
+ await GiftsSemaphore.WaitAsync().ConfigureAwait(false);
+ Task.Run(async () => {
+ await Task.Delay(Program.GlobalConfig.GiftsLimiterDelay*1000).ConfigureAwait(false);
+ GiftsSemaphore.Release();
+ }).Forget();
+ }
+
+ private static async Task LimitLoginRequestsAsync() {
+ await LoginSemaphore.WaitAsync().ConfigureAwait(false);
+ Task.Run(async () => {
+ await Task.Delay(Program.GlobalConfig.LoginLimiterDelay*1000).ConfigureAwait(false);
+ LoginSemaphore.Release();
+ }).Forget();
+ }
+
+ private void OnAccountInfo(SteamUser.AccountInfoCallback callback) {
+ if (callback == null) {
+ ArchiLogger.LogNullError(nameof(callback));
+ return;
}
- if (!IsConnectedAndLoggedOn) {
- if (KeepRunning) {
- return "Bot " + BotName + " is not connected.";
+ if (!BotConfig.FarmOffline) {
+ SteamFriends.SetPersonaState(EPersonaState.Online);
+ }
+ }
+
+ private void OnChatInvite(SteamFriends.ChatInviteCallback callback) {
+ if ((callback?.ChatRoomID == null) || (callback.PatronID == null)) {
+ ArchiLogger.LogNullError(nameof(callback) + " || " + nameof(callback.ChatRoomID) + " || " + nameof(callback.PatronID));
+ return;
+ }
+
+ if (!IsMaster(callback.PatronID)) {
+ return;
+ }
+
+ SteamFriends.JoinChat(callback.ChatRoomID);
+ }
+
+ private async void OnChatMsg(SteamFriends.ChatMsgCallback callback) {
+ if (callback == null) {
+ ArchiLogger.LogNullError(nameof(callback));
+ return;
+ }
+
+ if (callback.ChatMsgType != EChatEntryType.ChatMsg) {
+ return;
+ }
+
+ if ((callback.ChatRoomID == null) || (callback.ChatterID == null) || string.IsNullOrEmpty(callback.Message)) {
+ ArchiLogger.LogNullError(nameof(callback.ChatRoomID) + " || " + nameof(callback.ChatterID) + " || " + nameof(callback.Message));
+ return;
+ }
+
+ ArchiLogger.LogGenericTrace(callback.ChatRoomID.ConvertToUInt64() + "/" + callback.ChatterID.ConvertToUInt64() + ": " + callback.Message);
+
+ switch (callback.Message) {
+ case "!leave":
+ if (!IsMaster(callback.ChatterID)) {
+ break;
+ }
+
+ SteamFriends.LeaveChat(callback.ChatRoomID);
+ break;
+ default:
+ await HandleMessage(callback.ChatRoomID, callback.ChatterID, callback.Message).ConfigureAwait(false);
+ break;
+ }
+ }
+
+ private void OnConnected(SteamClient.ConnectedCallback callback) {
+ if (callback == null) {
+ ArchiLogger.LogNullError(nameof(callback));
+ return;
+ }
+
+ if (callback.Result != EResult.OK) {
+ ArchiLogger.LogGenericError("Unable to connect to Steam: " + callback.Result);
+ return;
+ }
+
+ ArchiLogger.LogGenericInfo("Connected to Steam!");
+
+ if (!KeepRunning) {
+ ArchiLogger.LogGenericInfo("Disconnecting...");
+ Disconnect();
+ return;
+ }
+
+ byte[] sentryFileHash = null;
+ if (File.Exists(SentryFile)) {
+ try {
+ byte[] sentryFileContent = File.ReadAllBytes(SentryFile);
+ sentryFileHash = SteamKit2.CryptoHelper.SHAHash(sentryFileContent);
+ } catch (Exception e) {
+ ArchiLogger.LogGenericException(e);
+ }
+ }
+
+ if (!InitializeLoginAndPassword(false)) {
+ Stop();
+ return;
+ }
+
+ ArchiLogger.LogGenericInfo("Logging in...");
+
+ string password = BotConfig.SteamPassword;
+ if (!string.IsNullOrEmpty(password)) {
+ // Steam silently ignores non-ASCII characters in password, we're going to do the same
+ // Don't ask me why, I know it's stupid
+ password = Regex.Replace(password, @"[^\u0000-\u007F]+", "");
+ }
+
+ // Decrypt login key if needed
+ string loginKey = BotDatabase.LoginKey;
+ if (!string.IsNullOrEmpty(loginKey) && (loginKey.Length > 19)) {
+ loginKey = CryptoHelper.Decrypt(BotConfig.PasswordFormat, loginKey);
+ }
+
+ SteamUser.LogOnDetails logOnDetails = new SteamUser.LogOnDetails { Username = BotConfig.SteamLogin, Password = password, AuthCode = AuthCode, CellID = Program.GlobalDatabase.CellID, LoginID = LoginID, LoginKey = loginKey, TwoFactorCode = TwoFactorCode, SentryFileHash = sentryFileHash, ShouldRememberPassword = true };
+
+ try {
+ SteamUser.LogOn(logOnDetails);
+ } catch {
+ // TODO: Remove me once https://github.com/SteamRE/SteamKit/issues/305 is fixed
+ ArchiHandler.LogOnWithoutMachineID(logOnDetails);
+ }
+ }
+
+ private async void OnDisconnected(SteamClient.DisconnectedCallback callback) {
+ if (callback == null) {
+ ArchiLogger.LogNullError(nameof(callback));
+ return;
+ }
+
+ HeartBeatFailures = 0;
+
+ EResult lastLogOnResult = LastLogOnResult;
+ LastLogOnResult = EResult.Invalid;
+
+ ArchiLogger.LogGenericInfo("Disconnected from Steam!");
+
+ ArchiWebHandler.OnDisconnected();
+ CardsFarmer.OnDisconnected();
+ Trading.OnDisconnected();
+
+ FirstTradeSent = false;
+ HandledGifts.ClearAndTrim();
+
+ // If we initiated disconnect, do not attempt to reconnect
+ if (callback.UserInitiated) {
+ return;
+ }
+
+ switch (lastLogOnResult) {
+ case EResult.Invalid:
+ // Invalid means that we didn't get OnLoggedOn() in the first place, so Steam is down
+ // Always reset one-time-only access tokens in this case, as OnLoggedOn() didn't do that for us
+ AuthCode = TwoFactorCode = null;
+ break;
+ case EResult.InvalidPassword:
+ // If we didn't use login key, it's nearly always rate limiting
+ if (string.IsNullOrEmpty(BotDatabase.LoginKey)) {
+ goto case EResult.RateLimitExceeded;
+ }
+
+ BotDatabase.LoginKey = null;
+ ArchiLogger.LogGenericInfo("Removed expired login key");
+ break;
+ case EResult.NoConnection:
+ case EResult.ServiceUnavailable:
+ case EResult.Timeout:
+ case EResult.TryAnotherCM:
+ await Task.Delay(5000).ConfigureAwait(false);
+ break;
+ case EResult.RateLimitExceeded:
+ ArchiLogger.LogGenericInfo("Will retry after 25 minutes...");
+ await Task.Delay(25*60*1000).ConfigureAwait(false); // Captcha disappears after around 20 minutes, so we make it 25
+ break;
+ }
+
+ if (!KeepRunning || SteamClient.IsConnected) {
+ return;
+ }
+
+ ArchiLogger.LogGenericInfo("Reconnecting...");
+ await Connect().ConfigureAwait(false);
+ }
+
+ private void OnFreeLicense(SteamApps.FreeLicenseCallback callback) {
+ if (callback == null) {
+ ArchiLogger.LogNullError(nameof(callback));
+ }
+ }
+
+ private async void OnFriendMsg(SteamFriends.FriendMsgCallback callback) {
+ if (callback == null) {
+ ArchiLogger.LogNullError(nameof(callback));
+ return;
+ }
+
+ if (callback.EntryType != EChatEntryType.ChatMsg) {
+ return;
+ }
+
+ if ((callback.Sender == null) || string.IsNullOrEmpty(callback.Message)) {
+ ArchiLogger.LogNullError(nameof(callback.Sender) + " || " + nameof(callback.Message));
+ return;
+ }
+
+ ArchiLogger.LogGenericTrace(callback.Sender.ConvertToUInt64() + ": " + callback.Message);
+
+ await HandleMessage(callback.Sender, callback.Sender, callback.Message).ConfigureAwait(false);
+ }
+
+ private async void OnFriendMsgHistory(SteamFriends.FriendMsgHistoryCallback callback) {
+ if ((callback?.Messages == null) || (callback.SteamID == null)) {
+ ArchiLogger.LogNullError(nameof(callback) + " || " + nameof(callback.Messages) + " || " + nameof(callback.SteamID));
+ return;
+ }
+
+ if ((callback.Messages.Count == 0) || !IsMaster(callback.SteamID)) {
+ return;
+ }
+
+ // Get last message
+ SteamFriends.FriendMsgHistoryCallback.FriendMessage lastMessage = callback.Messages[callback.Messages.Count - 1];
+
+ // If message is read already, return
+ if (!lastMessage.Unread) {
+ return;
+ }
+
+ // If message is too old, return
+ if (DateTime.UtcNow.Subtract(lastMessage.Timestamp).TotalHours > 1) {
+ return;
+ }
+
+ // Handle the message
+ await HandleMessage(callback.SteamID, callback.SteamID, lastMessage.Message).ConfigureAwait(false);
+ }
+
+ private void OnFriendsList(SteamFriends.FriendsListCallback callback) {
+ if (callback?.FriendList == null) {
+ ArchiLogger.LogNullError(nameof(callback) + " || " + nameof(callback.FriendList));
+ return;
+ }
+
+ foreach (SteamFriends.FriendsListCallback.Friend friend in callback.FriendList.Where(friend => friend.Relationship == EFriendRelationship.RequestRecipient)) {
+ if (IsMaster(friend.SteamID)) {
+ SteamFriends.AddFriend(friend.SteamID);
+ } else if (BotConfig.IsBotAccount) {
+ SteamFriends.RemoveFriend(friend.SteamID);
+ }
+ }
+ }
+
+ private async void OnGuestPassList(SteamApps.GuestPassListCallback callback) {
+ if (callback?.GuestPasses == null) {
+ ArchiLogger.LogNullError(nameof(callback) + " || " + nameof(callback.GuestPasses));
+ return;
+ }
+
+ if ((callback.CountGuestPassesToRedeem == 0) || (callback.GuestPasses.Count == 0) || !BotConfig.AcceptGifts) {
+ return;
+ }
+
+ foreach (ulong gid in callback.GuestPasses.Select(guestPass => guestPass["gid"].AsUnsignedLong()).Where(gid => (gid != 0) && !HandledGifts.Contains(gid))) {
+ HandledGifts.Add(gid);
+
+ ArchiLogger.LogGenericInfo("Accepting gift: " + gid + "...");
+ await LimitGiftsRequestsAsync().ConfigureAwait(false);
+
+ ArchiHandler.RedeemGuestPassResponseCallback response = await ArchiHandler.RedeemGuestPass(gid).ConfigureAwait(false);
+ if (response != null) {
+ if (response.Result == EResult.OK) {
+ ArchiLogger.LogGenericInfo("Success!");
+ } else {
+ ArchiLogger.LogGenericWarning("Failed with error: " + response.Result);
+ }
+ } else {
+ ArchiLogger.LogGenericWarning("Failed!");
+ }
+ }
+ }
+
+ private async void OnLicenseList(SteamApps.LicenseListCallback callback) {
+ if (callback?.LicenseList == null) {
+ ArchiLogger.LogNullError(nameof(callback) + " || " + nameof(callback.LicenseList));
+ return;
+ }
+
+ OwnedPackageIDs.Clear();
+
+ foreach (SteamApps.LicenseListCallback.License license in callback.LicenseList) {
+ OwnedPackageIDs.Add(license.PackageID);
+ }
+
+ OwnedPackageIDs.TrimExcess();
+
+ await Task.Delay(1000).ConfigureAwait(false); // Wait a second for eventual PlayingSessionStateCallback
+
+ if (!ArchiWebHandler.Ready) {
+ for (byte i = 0; (i < Program.GlobalConfig.HttpTimeout) && !ArchiWebHandler.Ready; i++) {
+ await Task.Delay(1000).ConfigureAwait(false);
}
- return "Bot " + BotName + " is not running.";
+ if (!ArchiWebHandler.Ready) {
+ return;
+ }
}
- if (PlayingBlocked) {
- return "Bot " + BotName + " is currently being used.";
+ await CardsFarmer.OnNewGameAdded().ConfigureAwait(false);
+ }
+
+ private void OnLoggedOff(SteamUser.LoggedOffCallback callback) {
+ if (callback == null) {
+ ArchiLogger.LogNullError(nameof(callback));
+ return;
}
- if (CardsFarmer.Paused) {
- return "Bot " + BotName + " is paused or running in manual mode.";
+ ArchiLogger.LogGenericInfo("Logged off of Steam: " + callback.Result);
+
+ switch (callback.Result) {
+ case EResult.LogonSessionReplaced:
+ ArchiLogger.LogGenericError("This account seems to be used in another ASF instance, which is undefined behaviour, refusing to keep it running!");
+ Stop();
+ break;
+ }
+ }
+
+ private async void OnLoggedOn(SteamUser.LoggedOnCallback callback) {
+ if (callback == null) {
+ ArchiLogger.LogNullError(nameof(callback));
+ return;
}
- if (CardsFarmer.CurrentGamesFarming.Count == 0) {
- return "Bot " + BotName + " is not farming anything.";
+ // Always reset one-time-only access tokens
+ AuthCode = TwoFactorCode = null;
+
+ // Keep LastLogOnResult for OnDisconnected()
+ LastLogOnResult = callback.Result;
+
+ switch (callback.Result) {
+ case EResult.AccountLogonDenied:
+ AuthCode = Program.GetUserInput(ASF.EUserInputType.SteamGuard, BotName);
+ if (string.IsNullOrEmpty(AuthCode)) {
+ Stop();
+ }
+
+ break;
+ case EResult.AccountLoginDeniedNeedTwoFactor:
+ if (BotDatabase.MobileAuthenticator == null) {
+ TwoFactorCode = Program.GetUserInput(ASF.EUserInputType.TwoFactorAuthentication, BotName);
+ if (string.IsNullOrEmpty(TwoFactorCode)) {
+ Stop();
+ }
+ } else {
+ ArchiLogger.LogGenericWarning("2FA code was invalid despite of using ASF 2FA. Invalid authenticator or bad timing?");
+ }
+
+ break;
+ case EResult.OK:
+ ArchiLogger.LogGenericInfo("Successfully logged on!");
+
+ // Old status for these doesn't matter, we'll update them if needed
+ LibraryLockedBySteamID = 0;
+ IsLimitedUser = PlayingBlocked = false;
+
+ if (callback.AccountFlags.HasFlag(EAccountFlags.LimitedUser)) {
+ IsLimitedUser = true;
+ ArchiLogger.LogGenericWarning("This account is limited, farming process is permanently unavailable until the restriction is removed!");
+ }
+
+ if ((callback.CellID != 0) && (Program.GlobalDatabase.CellID != callback.CellID)) {
+ Program.GlobalDatabase.CellID = callback.CellID;
+ }
+
+ if (BotDatabase.MobileAuthenticator == null) {
+ // Support and convert SDA files
+ string maFilePath = Path.Combine(SharedInfo.ConfigDirectory, callback.ClientSteamID.ConvertToUInt64() + ".maFile");
+ if (File.Exists(maFilePath)) {
+ ImportAuthenticator(maFilePath);
+ }
+ }
+
+ if (string.IsNullOrEmpty(BotConfig.SteamParentalPIN)) {
+ BotConfig.SteamParentalPIN = Program.GetUserInput(ASF.EUserInputType.SteamParentalPIN, BotName);
+ if (string.IsNullOrEmpty(BotConfig.SteamParentalPIN)) {
+ Stop();
+ return;
+ }
+ }
+
+ if (!await ArchiWebHandler.Init(callback.ClientSteamID, SteamClient.ConnectedUniverse, callback.WebAPIUserNonce, BotConfig.SteamParentalPIN).ConfigureAwait(false)) {
+ if (!await RefreshSession().ConfigureAwait(false)) {
+ return;
+ }
+ }
+
+ InitializeFamilySharing().Forget();
+
+ if (BotConfig.DismissInventoryNotifications) {
+ ArchiWebHandler.MarkInventory().Forget();
+ }
+
+ if (BotConfig.SteamMasterClanID != 0) {
+ Task.Run(async () => {
+ await ArchiWebHandler.JoinGroup(BotConfig.SteamMasterClanID).ConfigureAwait(false);
+ JoinMasterChat();
+ }).Forget();
+ }
+
+ if (Program.GlobalConfig.Statistics) {
+ ArchiWebHandler.JoinGroup(SharedInfo.ASFGroupSteamID).Forget();
+ }
+
+ Trading.CheckTrades().Forget();
+ break;
+ case EResult.InvalidPassword:
+ case EResult.NoConnection:
+ case EResult.RateLimitExceeded:
+ case EResult.ServiceUnavailable:
+ case EResult.Timeout:
+ case EResult.TryAnotherCM:
+ case EResult.TwoFactorCodeMismatch:
+ ArchiLogger.LogGenericWarning("Unable to login to Steam: " + callback.Result + " / " + callback.ExtendedResult);
+ break;
+ default: // Unexpected result, shutdown immediately
+ ArchiLogger.LogGenericError("Unable to login to Steam: " + callback.Result + " / " + callback.ExtendedResult);
+ Stop();
+ break;
+ }
+ }
+
+ private void OnLoginKey(SteamUser.LoginKeyCallback callback) {
+ if (string.IsNullOrEmpty(callback?.LoginKey)) {
+ ArchiLogger.LogNullError(nameof(callback) + " || " + nameof(callback.LoginKey));
+ return;
}
- StringBuilder response = new StringBuilder("Bot " + BotName + " is farming ");
+ string loginKey = callback.LoginKey;
+ if (!string.IsNullOrEmpty(loginKey)) {
+ loginKey = CryptoHelper.Encrypt(BotConfig.PasswordFormat, loginKey);
+ }
- if (CardsFarmer.CurrentGamesFarming.Count == 1) {
- CardsFarmer.Game game = CardsFarmer.CurrentGamesFarming.First();
- response.Append("game " + game.AppID + " (" + game.GameName + ", " + game.CardsRemaining + " card drops remaining)");
+ BotDatabase.LoginKey = loginKey;
+ SteamUser.AcceptNewLoginKey(callback);
+ }
+
+ private void OnMachineAuth(SteamUser.UpdateMachineAuthCallback callback) {
+ if (callback == null) {
+ ArchiLogger.LogNullError(nameof(callback));
+ return;
+ }
+
+ int fileSize;
+ byte[] sentryHash;
+
+ try {
+ using (FileStream fileStream = File.Open(SentryFile, FileMode.OpenOrCreate, FileAccess.ReadWrite)) {
+ fileStream.Seek(callback.Offset, SeekOrigin.Begin);
+ fileStream.Write(callback.Data, 0, callback.BytesToWrite);
+ fileSize = (int) fileStream.Length;
+
+ fileStream.Seek(0, SeekOrigin.Begin);
+ using (SHA1CryptoServiceProvider sha = new SHA1CryptoServiceProvider()) {
+ sentryHash = sha.ComputeHash(fileStream);
+ }
+ }
+ } catch (Exception e) {
+ ArchiLogger.LogGenericException(e);
+ return;
+ }
+
+ // Inform the steam servers that we're accepting this sentry file
+ SteamUser.SendMachineAuthResponse(new SteamUser.MachineAuthDetails { JobID = callback.JobID, FileName = callback.FileName, BytesWritten = callback.BytesToWrite, FileSize = fileSize, Offset = callback.Offset, Result = EResult.OK, LastError = 0, OneTimePassword = callback.OneTimePassword, SentryFileHash = sentryHash });
+ }
+
+ private void OnNotifications(ArchiHandler.NotificationsCallback callback) {
+ if (callback == null) {
+ ArchiLogger.LogNullError(nameof(callback));
+ return;
+ }
+
+ if ((callback.Notifications == null) || (callback.Notifications.Count == 0)) {
+ return;
+ }
+
+ foreach (ArchiHandler.NotificationsCallback.ENotification notification in callback.Notifications) {
+ switch (notification) {
+ case ArchiHandler.NotificationsCallback.ENotification.Items:
+ CardsFarmer.OnNewItemsNotification().Forget();
+ if (BotConfig.DismissInventoryNotifications) {
+ ArchiWebHandler.MarkInventory().Forget();
+ }
+ break;
+ case ArchiHandler.NotificationsCallback.ENotification.Trading:
+ Trading.CheckTrades().Forget();
+ break;
+ }
+ }
+ }
+
+ private void OnOfflineMessage(ArchiHandler.OfflineMessageCallback callback) {
+ if (callback == null) {
+ ArchiLogger.LogNullError(nameof(callback));
+ return;
+ }
+
+ if ((callback.OfflineMessagesCount == 0) || !BotConfig.HandleOfflineMessages) {
+ return;
+ }
+
+ SteamFriends.RequestOfflineMessages();
+ }
+
+ private void OnPersonaState(SteamFriends.PersonaStateCallback callback) {
+ if (callback == null) {
+ ArchiLogger.LogNullError(nameof(callback));
+ return;
+ }
+
+ if (callback.FriendID == SteamClient.SteamID) {
+ Events.OnStateUpdated(this, callback);
+ } else if ((callback.FriendID == LibraryLockedBySteamID) && (callback.GameID == 0)) {
+ LibraryLockedBySteamID = 0;
+ CheckOccupationStatus();
+ }
+ }
+
+ private void OnPlayingSessionState(ArchiHandler.PlayingSessionStateCallback callback) {
+ if (callback == null) {
+ ArchiLogger.LogNullError(nameof(callback));
+ return;
+ }
+
+ if (callback.PlayingBlocked == PlayingBlocked) {
+ return; // No status update, we're not interested
+ }
+
+ PlayingBlocked = callback.PlayingBlocked;
+ CheckOccupationStatus();
+ }
+
+ private void OnPurchaseResponse(ArchiHandler.PurchaseResponseCallback callback) {
+ if (callback == null) {
+ ArchiLogger.LogNullError(nameof(callback));
+ }
+ }
+
+ private void OnSharedLibraryLockStatus(ArchiHandler.SharedLibraryLockStatusCallback callback) {
+ if (callback == null) {
+ ArchiLogger.LogNullError(nameof(callback));
+ return;
+ }
+
+ // Ignore no status updates
+ if (LibraryLockedBySteamID == 0) {
+ if ((callback.LibraryLockedBySteamID == 0) || (callback.LibraryLockedBySteamID == SteamClient.SteamID)) {
+ return;
+ }
+
+ LibraryLockedBySteamID = callback.LibraryLockedBySteamID;
} else {
- response.Append("appIDs " + string.Join(", ", CardsFarmer.CurrentGamesFarming.Select(game => game.AppID)));
- }
-
- response.Append(" and has a total of " + CardsFarmer.GamesToFarm.Count + " games (" + CardsFarmer.GamesToFarm.Sum(game => game.CardsRemaining) + " cards) left to farm.");
- return response.ToString();
- }
-
- private static string ResponseStatus(ulong steamID, string botName) {
- if ((steamID == 0) || string.IsNullOrEmpty(botName)) {
- ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botName));
- return null;
- }
-
- Bot bot;
- if (Bots.TryGetValue(botName, out bot)) {
- return bot.ResponseStatus(steamID);
- }
-
- if (IsOwner(steamID)) {
- return "Couldn't find any bot named " + botName + "!";
- }
-
- return null;
- }
-
- private static string ResponseStatusAll(ulong steamID) {
- if (steamID == 0) {
- ASF.ArchiLogger.LogNullError(nameof(steamID));
- return null;
- }
-
- if (!IsOwner(steamID)) {
- return null;
- }
-
- HashSet botsRunning = new HashSet(Bots.Where(bot => bot.Value.KeepRunning).OrderBy(bot => bot.Key).Select(bot => bot.Value));
- IEnumerable statuses = botsRunning.Select(bot => bot.ResponseStatus(steamID));
-
- return Environment.NewLine +
- string.Join(Environment.NewLine, statuses) + Environment.NewLine +
- "There are " + botsRunning.Count + "/" + Bots.Count + " bots running, with total of " + botsRunning.Sum(bot => bot.CardsFarmer.GamesToFarm.Count) + " games (" + botsRunning.Sum(bot => bot.CardsFarmer.GamesToFarm.Sum(game => game.CardsRemaining)) + " cards) left to farm.";
- }
-
- private async Task ResponseLoot(ulong steamID) {
- if (steamID == 0) {
- ArchiLogger.LogNullError(nameof(steamID));
- return null;
- }
-
- if (!IsMaster(steamID)) {
- return null;
- }
-
- if (!IsConnectedAndLoggedOn) {
- return "This bot instance is not connected!";
- }
-
- if (BotConfig.SteamMasterID == 0) {
- return "Trade couldn't be send because SteamMasterID is not defined!";
- }
-
- if (BotConfig.SteamMasterID == SteamClient.SteamID) {
- return "You can't loot yourself!";
- }
-
- await Trading.LimitInventoryRequestsAsync().ConfigureAwait(false);
-
- HashSet inventory = await ArchiWebHandler.GetMySteamInventory(true).ConfigureAwait(false);
- if ((inventory == null) || (inventory.Count == 0)) {
- return "Nothing to send, inventory seems empty!";
- }
-
- // Remove from our pending inventory all items that are not steam cards and boosters
- if (inventory.RemoveWhere(item => (item.Type != Steam.Item.EType.TradingCard) && ((item.Type != Steam.Item.EType.FoilTradingCard) || !BotConfig.IsBotAccount) && (item.Type != Steam.Item.EType.BoosterPack)) > 0) {
- if (inventory.Count == 0) {
- return "Nothing to send, inventory seems empty!";
+ if ((callback.LibraryLockedBySteamID != 0) && (callback.LibraryLockedBySteamID != SteamClient.SteamID)) {
+ return;
}
+
+ if (SteamFriends.GetFriendGamePlayed(LibraryLockedBySteamID) != 0) {
+ return;
+ }
+
+ LibraryLockedBySteamID = 0;
}
- if (!await ArchiWebHandler.SendTradeOffer(inventory, BotConfig.SteamMasterID, BotConfig.SteamTradeToken).ConfigureAwait(false)) {
- return "Trade offer failed due to error!";
- }
-
- await Task.Delay(1000).ConfigureAwait(false); // Sometimes we can be too fast for Steam servers to generate confirmations, wait a short moment
- await AcceptConfirmations(true, Steam.ConfirmationDetails.EType.Trade, BotConfig.SteamMasterID).ConfigureAwait(false);
- return "Trade offer sent successfully!";
+ CheckOccupationStatus();
}
- private static async Task ResponseLoot(ulong steamID, string botName) {
- if ((steamID == 0) || string.IsNullOrEmpty(botName)) {
- ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botName));
- return null;
+ private void OnWebAPIUserNonce(SteamUser.WebAPIUserNonceCallback callback) {
+ if (callback == null) {
+ ArchiLogger.LogNullError(nameof(callback));
}
-
- Bot bot;
- if (Bots.TryGetValue(botName, out bot)) {
- return await bot.ResponseLoot(steamID).ConfigureAwait(false);
- }
-
- if (IsOwner(steamID)) {
- return "Couldn't find any bot named " + botName + "!";
- }
-
- return null;
}
- private static async Task ResponseLootAll(ulong steamID) {
- if (steamID == 0) {
- ASF.ArchiLogger.LogNullError(nameof(steamID));
- return null;
+ private void ResetGamesPlayed() {
+ if (PlayingBlocked) {
+ return;
}
- if (!IsOwner(steamID)) {
- return null;
- }
-
- await Task.WhenAll(Bots.Values.Where(bot => bot.IsConnectedAndLoggedOn).Select(bot => bot.ResponseLoot(steamID))).ConfigureAwait(false);
- return "Done!";
+ ArchiHandler.PlayGames(BotConfig.GamesPlayedWhileIdle, BotConfig.CustomGamePlayedWhileIdle);
}
private async Task Response2FA(ulong steamID) {
@@ -1159,6 +1459,74 @@ namespace ArchiSteamFarm {
return null;
}
+ private async Task ResponseAddLicense(ulong steamID, ICollection gameIDs) {
+ if ((steamID == 0) || (gameIDs == null) || (gameIDs.Count == 0)) {
+ ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(gameIDs) + " || " + nameof(gameIDs.Count));
+ return null;
+ }
+
+ if (!IsMaster(steamID)) {
+ return null;
+ }
+
+ if (!IsConnectedAndLoggedOn) {
+ return "This bot instance is not connected!";
+ }
+
+ StringBuilder result = new StringBuilder();
+ foreach (uint gameID in gameIDs) {
+ SteamApps.FreeLicenseCallback callback = await SteamApps.RequestFreeLicense(gameID);
+ if (callback == null) {
+ result.AppendLine(Environment.NewLine + "Result: Timeout!");
+ break;
+ }
+
+ if ((callback.GrantedApps.Count != 0) || (callback.GrantedPackages.Count != 0)) {
+ result.AppendLine(Environment.NewLine + "Result: " + callback.Result + " | Granted apps:" + (callback.GrantedApps.Count != 0 ? " " + string.Join(", ", callback.GrantedApps) : "") + (callback.GrantedPackages.Count != 0 ? " " + string.Join(", ", callback.GrantedPackages) : ""));
+ } else if (await ArchiWebHandler.AddFreeLicense(gameID).ConfigureAwait(false)) {
+ result.AppendLine(Environment.NewLine + "Result: " + EResult.OK + " | Granted apps: " + gameID);
+ } else {
+ result.AppendLine(Environment.NewLine + "Result: " + EResult.AccessDenied);
+ }
+ }
+
+ return result.ToString();
+ }
+
+ private static async Task ResponseAddLicense(ulong steamID, string botName, string games) {
+ if ((steamID == 0) || string.IsNullOrEmpty(botName) || string.IsNullOrEmpty(games)) {
+ ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botName) + " || " + nameof(games));
+ return null;
+ }
+
+ Bot bot;
+ if (!Bots.TryGetValue(botName, out bot)) {
+ if (IsOwner(steamID)) {
+ return "Couldn't find any bot named " + botName + "!";
+ }
+
+ return null;
+ }
+
+ string[] gameIDs = games.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
+
+ HashSet gamesToRedeem = new HashSet();
+ foreach (string game in gameIDs.Where(game => !string.IsNullOrEmpty(game))) {
+ uint gameID;
+ if (!uint.TryParse(game, out gameID)) {
+ return "Couldn't parse games list!";
+ }
+
+ gamesToRedeem.Add(gameID);
+ }
+
+ if (gamesToRedeem.Count == 0) {
+ return "List of games is empty!";
+ }
+
+ return await bot.ResponseAddLicense(steamID, gamesToRedeem).ConfigureAwait(false);
+ }
+
private static string ResponseAPI(ulong steamID) {
if (steamID != 0) {
return !IsOwner(steamID) ? null : GetAPIStatus();
@@ -1237,6 +1605,317 @@ namespace ArchiSteamFarm {
return "https://github.com/" + SharedInfo.GithubRepo + "/wiki/Commands";
}
+ private async Task ResponseLoot(ulong steamID) {
+ if (steamID == 0) {
+ ArchiLogger.LogNullError(nameof(steamID));
+ return null;
+ }
+
+ if (!IsMaster(steamID)) {
+ return null;
+ }
+
+ if (!IsConnectedAndLoggedOn) {
+ return "This bot instance is not connected!";
+ }
+
+ if (BotConfig.SteamMasterID == 0) {
+ return "Trade couldn't be send because SteamMasterID is not defined!";
+ }
+
+ if (BotConfig.SteamMasterID == SteamClient.SteamID) {
+ return "You can't loot yourself!";
+ }
+
+ await Trading.LimitInventoryRequestsAsync().ConfigureAwait(false);
+
+ HashSet inventory = await ArchiWebHandler.GetMySteamInventory(true).ConfigureAwait(false);
+ if ((inventory == null) || (inventory.Count == 0)) {
+ return "Nothing to send, inventory seems empty!";
+ }
+
+ // Remove from our pending inventory all items that are not steam cards and boosters
+ if (inventory.RemoveWhere(item => (item.Type != Steam.Item.EType.TradingCard) && ((item.Type != Steam.Item.EType.FoilTradingCard) || !BotConfig.IsBotAccount) && (item.Type != Steam.Item.EType.BoosterPack)) > 0) {
+ if (inventory.Count == 0) {
+ return "Nothing to send, inventory seems empty!";
+ }
+ }
+
+ if (!await ArchiWebHandler.SendTradeOffer(inventory, BotConfig.SteamMasterID, BotConfig.SteamTradeToken).ConfigureAwait(false)) {
+ return "Trade offer failed due to error!";
+ }
+
+ await Task.Delay(1000).ConfigureAwait(false); // Sometimes we can be too fast for Steam servers to generate confirmations, wait a short moment
+ await AcceptConfirmations(true, Steam.ConfirmationDetails.EType.Trade, BotConfig.SteamMasterID).ConfigureAwait(false);
+ return "Trade offer sent successfully!";
+ }
+
+ private static async Task ResponseLoot(ulong steamID, string botName) {
+ if ((steamID == 0) || string.IsNullOrEmpty(botName)) {
+ ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botName));
+ return null;
+ }
+
+ Bot bot;
+ if (Bots.TryGetValue(botName, out bot)) {
+ return await bot.ResponseLoot(steamID).ConfigureAwait(false);
+ }
+
+ if (IsOwner(steamID)) {
+ return "Couldn't find any bot named " + botName + "!";
+ }
+
+ return null;
+ }
+
+ private static async Task ResponseLootAll(ulong steamID) {
+ if (steamID == 0) {
+ ASF.ArchiLogger.LogNullError(nameof(steamID));
+ return null;
+ }
+
+ if (!IsOwner(steamID)) {
+ return null;
+ }
+
+ await Task.WhenAll(Bots.Values.Where(bot => bot.IsConnectedAndLoggedOn).Select(bot => bot.ResponseLoot(steamID))).ConfigureAwait(false);
+ return "Done!";
+ }
+
+ private async Task ResponseOwns(ulong steamID, string query) {
+ if ((steamID == 0) || string.IsNullOrEmpty(query)) {
+ ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(query));
+ return null;
+ }
+
+ if (!IsMaster(steamID)) {
+ return null;
+ }
+
+ if (!IsConnectedAndLoggedOn) {
+ return "This bot instance is not connected!";
+ }
+
+ Dictionary ownedGames;
+ if (!string.IsNullOrEmpty(BotConfig.SteamApiKey)) {
+ ownedGames = ArchiWebHandler.GetOwnedGames(SteamClient.SteamID);
+ } else {
+ ownedGames = await ArchiWebHandler.GetOwnedGames().ConfigureAwait(false);
+ }
+
+ if ((ownedGames == null) || (ownedGames.Count == 0)) {
+ return Environment.NewLine + "<" + BotName + "> List of owned games is empty!";
+ }
+
+ StringBuilder response = new StringBuilder();
+
+ string[] games = query.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
+ foreach (string game in games.Where(game => !string.IsNullOrEmpty(game))) {
+ // Check if this is appID
+ uint appID;
+ if (uint.TryParse(game, out appID)) {
+ string ownedName;
+ if (ownedGames.TryGetValue(appID, out ownedName)) {
+ response.Append(Environment.NewLine + "<" + BotName + "> Owned already: " + appID + " | " + ownedName);
+ } else {
+ response.Append(Environment.NewLine + "<" + BotName + "> Not owned yet: " + appID);
+ }
+
+ continue;
+ }
+
+ // This is a string, so check our entire library
+ foreach (KeyValuePair ownedGame in ownedGames.Where(ownedGame => ownedGame.Value.IndexOf(game, StringComparison.OrdinalIgnoreCase) >= 0)) {
+ response.Append(Environment.NewLine + "<" + BotName + "> Owned already: " + ownedGame.Key + " | " + ownedGame.Value);
+ }
+ }
+
+ if (response.Length > 0) {
+ return response.ToString();
+ }
+
+ return Environment.NewLine + "<" + BotName + "> Not owned yet: " + query;
+ }
+
+ private static async Task ResponseOwns(ulong steamID, string botName, string query) {
+ if ((steamID == 0) || string.IsNullOrEmpty(botName) || string.IsNullOrEmpty(query)) {
+ ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botName) + " || " + nameof(query));
+ return null;
+ }
+
+ Bot bot;
+ if (Bots.TryGetValue(botName, out bot)) {
+ return await bot.ResponseOwns(steamID, query).ConfigureAwait(false);
+ }
+
+ if (IsOwner(steamID)) {
+ return "Couldn't find any bot named " + botName + "!";
+ }
+
+ return null;
+ }
+
+ private static async Task ResponseOwnsAll(ulong steamID, string query) {
+ if ((steamID == 0) || string.IsNullOrEmpty(query)) {
+ ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(query));
+ return null;
+ }
+
+ if (!IsOwner(steamID)) {
+ return null;
+ }
+
+ string[] responses = await Task.WhenAll(Bots.Where(bot => bot.Value.IsConnectedAndLoggedOn).OrderBy(bot => bot.Key).Select(bot => bot.Value.ResponseOwns(steamID, query))).ConfigureAwait(false);
+
+ StringBuilder result = new StringBuilder();
+ foreach (string response in responses.Where(response => !string.IsNullOrEmpty(response))) {
+ result.Append(response);
+ }
+
+ return result.Length != 0 ? result.ToString() : null;
+ }
+
+ private string ResponsePassword(ulong steamID) {
+ if (steamID == 0) {
+ ArchiLogger.LogNullError(nameof(steamID));
+ return null;
+ }
+
+ if (!IsMaster(steamID)) {
+ return null;
+ }
+
+ if (string.IsNullOrEmpty(BotConfig.SteamPassword)) {
+ return "Can't encrypt null password!";
+ }
+
+ return Environment.NewLine + "[" + CryptoHelper.ECryptoMethod.AES + "] password: " + CryptoHelper.Encrypt(CryptoHelper.ECryptoMethod.AES, BotConfig.SteamPassword) + Environment.NewLine + "[" + CryptoHelper.ECryptoMethod.ProtectedDataForCurrentUser + "] password: " + CryptoHelper.Encrypt(CryptoHelper.ECryptoMethod.ProtectedDataForCurrentUser, BotConfig.SteamPassword);
+ }
+
+ private static string ResponsePassword(ulong steamID, string botName) {
+ if ((steamID == 0) || string.IsNullOrEmpty(botName)) {
+ ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botName));
+ return null;
+ }
+
+ Bot bot;
+ if (Bots.TryGetValue(botName, out bot)) {
+ return bot.ResponsePassword(steamID);
+ }
+
+ if (IsOwner(steamID)) {
+ return "Couldn't find any bot named " + botName + "!";
+ }
+
+ return null;
+ }
+
+ private async Task ResponsePause(ulong steamID, bool sticky) {
+ if (steamID == 0) {
+ ArchiLogger.LogNullError(nameof(steamID));
+ return null;
+ }
+
+ if (!IsMaster(steamID) && !SteamFamilySharingIDs.Contains(steamID)) {
+ return null;
+ }
+
+ if (!IsConnectedAndLoggedOn) {
+ return "This bot instance is not connected!";
+ }
+
+ if (CardsFarmer.Paused) {
+ return "Automatic farming is paused already!";
+ }
+
+ await CardsFarmer.Pause(sticky).ConfigureAwait(false);
+
+ if (!SteamFamilySharingIDs.Contains(steamID)) {
+ return "Automatic farming is now paused!";
+ }
+
+ StartFamilySharingInactivityTimer();
+ return "Automatic farming is now paused! You have " + FamilySharingInactivityMinutes + " minutes to start a game.";
+ }
+
+ private static async Task ResponsePause(ulong steamID, string botName, bool sticky) {
+ if ((steamID == 0) || string.IsNullOrEmpty(botName)) {
+ ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botName));
+ return null;
+ }
+
+ Bot bot;
+ if (Bots.TryGetValue(botName, out bot)) {
+ return await bot.ResponsePause(steamID, sticky).ConfigureAwait(false);
+ }
+
+ if (IsOwner(steamID)) {
+ return "Couldn't find any bot named " + botName + "!";
+ }
+
+ return null;
+ }
+
+ private async Task ResponsePlay(ulong steamID, HashSet gameIDs) {
+ if ((steamID == 0) || (gameIDs == null) || (gameIDs.Count == 0)) {
+ ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(gameIDs) + " || " + nameof(gameIDs.Count));
+ return null;
+ }
+
+ if (!IsMaster(steamID)) {
+ return null;
+ }
+
+ if (!IsConnectedAndLoggedOn) {
+ return "This bot instance is not connected!";
+ }
+
+ if (!CardsFarmer.Paused) {
+ await CardsFarmer.Pause(false).ConfigureAwait(false);
+ }
+
+ ArchiHandler.PlayGames(gameIDs);
+ return "Done!";
+ }
+
+ private static async Task ResponsePlay(ulong steamID, string botName, string games) {
+ if ((steamID == 0) || string.IsNullOrEmpty(botName) || string.IsNullOrEmpty(games)) {
+ ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botName) + " || " + nameof(games));
+ return null;
+ }
+
+ Bot bot;
+ if (!Bots.TryGetValue(botName, out bot)) {
+ if (IsOwner(steamID)) {
+ return "Couldn't find any bot named " + botName + "!";
+ }
+
+ return null;
+ }
+
+ string[] gameIDs = games.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
+
+ HashSet gamesToPlay = new HashSet();
+ foreach (string game in gameIDs.Where(game => !string.IsNullOrEmpty(game))) {
+ uint gameID;
+ if (!uint.TryParse(game, out gameID)) {
+ return "Couldn't parse games list!";
+ }
+
+ gamesToPlay.Add(gameID);
+
+ if (gamesToPlay.Count >= CardsFarmer.MaxGamesPlayedConcurrently) {
+ break;
+ }
+ }
+
+ if (gamesToPlay.Count == 0) {
+ return "List of games is empty!";
+ }
+
+ return await bot.ResponsePlay(steamID, gamesToPlay).ConfigureAwait(false);
+ }
+
[SuppressMessage("ReSharper", "FunctionComplexityOverflow")]
private async Task ResponseRedeem(ulong steamID, string message, ERedeemFlags redeemFlags = ERedeemFlags.None) {
if ((steamID == 0) || string.IsNullOrEmpty(message)) {
@@ -1416,191 +2095,13 @@ namespace ArchiSteamFarm {
return "Done!";
}
- private static string ResponseStartAll(ulong steamID) {
+ private string ResponseResume(ulong steamID) {
if (steamID == 0) {
- ASF.ArchiLogger.LogNullError(nameof(steamID));
+ ArchiLogger.LogNullError(nameof(steamID));
return null;
}
- if (!IsOwner(steamID)) {
- return null;
- }
-
- foreach (Bot bot in Bots.Values.Where(bot => !bot.KeepRunning)) {
- bot.ResponseStart(steamID);
- }
-
- return "Done!";
- }
-
- private async Task ResponseAddLicense(ulong steamID, ICollection gameIDs) {
- if ((steamID == 0) || (gameIDs == null) || (gameIDs.Count == 0)) {
- ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(gameIDs) + " || " + nameof(gameIDs.Count));
- return null;
- }
-
- if (!IsMaster(steamID)) {
- return null;
- }
-
- if (!IsConnectedAndLoggedOn) {
- return "This bot instance is not connected!";
- }
-
- StringBuilder result = new StringBuilder();
- foreach (uint gameID in gameIDs) {
- SteamApps.FreeLicenseCallback callback = await SteamApps.RequestFreeLicense(gameID);
- if (callback == null) {
- result.AppendLine(Environment.NewLine + "Result: Timeout!");
- break;
- }
-
- if ((callback.GrantedApps.Count != 0) || (callback.GrantedPackages.Count != 0)) {
- result.AppendLine(Environment.NewLine + "Result: " + callback.Result + " | Granted apps:" + (callback.GrantedApps.Count != 0 ? " " + string.Join(", ", callback.GrantedApps) : "") + (callback.GrantedPackages.Count != 0 ? " " + string.Join(", ", callback.GrantedPackages) : ""));
- } else if (await ArchiWebHandler.AddFreeLicense(gameID).ConfigureAwait(false)) {
- result.AppendLine(Environment.NewLine + "Result: " + EResult.OK + " | Granted apps: " + gameID);
- } else {
- result.AppendLine(Environment.NewLine + "Result: " + EResult.AccessDenied);
- }
- }
-
- return result.ToString();
- }
-
- private static async Task ResponseAddLicense(ulong steamID, string botName, string games) {
- if ((steamID == 0) || string.IsNullOrEmpty(botName) || string.IsNullOrEmpty(games)) {
- ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botName) + " || " + nameof(games));
- return null;
- }
-
- Bot bot;
- if (!Bots.TryGetValue(botName, out bot)) {
- if (IsOwner(steamID)) {
- return "Couldn't find any bot named " + botName + "!";
- }
-
- return null;
- }
-
- string[] gameIDs = games.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
-
- HashSet gamesToRedeem = new HashSet();
- foreach (string game in gameIDs.Where(game => !string.IsNullOrEmpty(game))) {
- uint gameID;
- if (!uint.TryParse(game, out gameID)) {
- return "Couldn't parse games list!";
- }
-
- gamesToRedeem.Add(gameID);
- }
-
- if (gamesToRedeem.Count == 0) {
- return "List of games is empty!";
- }
-
- return await bot.ResponseAddLicense(steamID, gamesToRedeem).ConfigureAwait(false);
- }
-
- private async Task ResponseOwns(ulong steamID, string query) {
- if ((steamID == 0) || string.IsNullOrEmpty(query)) {
- ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(query));
- return null;
- }
-
- if (!IsMaster(steamID)) {
- return null;
- }
-
- if (!IsConnectedAndLoggedOn) {
- return "This bot instance is not connected!";
- }
-
- Dictionary ownedGames;
- if (!string.IsNullOrEmpty(BotConfig.SteamApiKey)) {
- ownedGames = ArchiWebHandler.GetOwnedGames(SteamClient.SteamID);
- } else {
- ownedGames = await ArchiWebHandler.GetOwnedGames().ConfigureAwait(false);
- }
-
- if ((ownedGames == null) || (ownedGames.Count == 0)) {
- return Environment.NewLine + "<" + BotName + "> List of owned games is empty!";
- }
-
- StringBuilder response = new StringBuilder();
-
- string[] games = query.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
- foreach (string game in games.Where(game => !string.IsNullOrEmpty(game))) {
- // Check if this is appID
- uint appID;
- if (uint.TryParse(game, out appID)) {
- string ownedName;
- if (ownedGames.TryGetValue(appID, out ownedName)) {
- response.Append(Environment.NewLine + "<" + BotName + "> Owned already: " + appID + " | " + ownedName);
- } else {
- response.Append(Environment.NewLine + "<" + BotName + "> Not owned yet: " + appID);
- }
-
- continue;
- }
-
- // This is a string, so check our entire library
- foreach (KeyValuePair ownedGame in ownedGames.Where(ownedGame => ownedGame.Value.IndexOf(game, StringComparison.OrdinalIgnoreCase) >= 0)) {
- response.Append(Environment.NewLine + "<" + BotName + "> Owned already: " + ownedGame.Key + " | " + ownedGame.Value);
- }
- }
-
- if (response.Length > 0) {
- return response.ToString();
- }
-
- return Environment.NewLine + "<" + BotName + "> Not owned yet: " + query;
- }
-
- private static async Task ResponseOwns(ulong steamID, string botName, string query) {
- if ((steamID == 0) || string.IsNullOrEmpty(botName) || string.IsNullOrEmpty(query)) {
- ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botName) + " || " + nameof(query));
- return null;
- }
-
- Bot bot;
- if (Bots.TryGetValue(botName, out bot)) {
- return await bot.ResponseOwns(steamID, query).ConfigureAwait(false);
- }
-
- if (IsOwner(steamID)) {
- return "Couldn't find any bot named " + botName + "!";
- }
-
- return null;
- }
-
- private static async Task ResponseOwnsAll(ulong steamID, string query) {
- if ((steamID == 0) || string.IsNullOrEmpty(query)) {
- ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(query));
- return null;
- }
-
- if (!IsOwner(steamID)) {
- return null;
- }
-
- string[] responses = await Task.WhenAll(Bots.Where(bot => bot.Value.IsConnectedAndLoggedOn).OrderBy(bot => bot.Key).Select(bot => bot.Value.ResponseOwns(steamID, query))).ConfigureAwait(false);
-
- StringBuilder result = new StringBuilder();
- foreach (string response in responses.Where(response => !string.IsNullOrEmpty(response))) {
- result.Append(response);
- }
-
- return result.Length != 0 ? result.ToString() : null;
- }
-
- private async Task ResponsePlay(ulong steamID, HashSet gameIDs) {
- if ((steamID == 0) || (gameIDs == null) || (gameIDs.Count == 0)) {
- ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(gameIDs) + " || " + nameof(gameIDs.Count));
- return null;
- }
-
- if (!IsMaster(steamID)) {
+ if (!IsMaster(steamID) && !SteamFamilySharingIDs.Contains(steamID)) {
return null;
}
@@ -1609,49 +2110,30 @@ namespace ArchiSteamFarm {
}
if (!CardsFarmer.Paused) {
- await CardsFarmer.Pause(false).ConfigureAwait(false);
+ return "Automatic farming is resumed already!";
}
- ArchiHandler.PlayGames(gameIDs);
- return "Done!";
+ StopFamilySharingInactivityTimer();
+ CardsFarmer.Resume(true);
+ return "Automatic farming is now resumed!";
}
- private static async Task ResponsePlay(ulong steamID, string botName, string games) {
- if ((steamID == 0) || string.IsNullOrEmpty(botName) || string.IsNullOrEmpty(games)) {
- ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botName) + " || " + nameof(games));
+ private static string ResponseResume(ulong steamID, string botName) {
+ if ((steamID == 0) || string.IsNullOrEmpty(botName)) {
+ ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botName));
return null;
}
Bot bot;
- if (!Bots.TryGetValue(botName, out bot)) {
- if (IsOwner(steamID)) {
- return "Couldn't find any bot named " + botName + "!";
- }
-
- return null;
+ if (Bots.TryGetValue(botName, out bot)) {
+ return bot.ResponseResume(steamID);
}
- string[] gameIDs = games.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
-
- HashSet gamesToPlay = new HashSet();
- foreach (string game in gameIDs.Where(game => !string.IsNullOrEmpty(game))) {
- uint gameID;
- if (!uint.TryParse(game, out gameID)) {
- return "Couldn't parse games list!";
- }
-
- gamesToPlay.Add(gameID);
-
- if (gamesToPlay.Count >= CardsFarmer.MaxGamesPlayedConcurrently) {
- break;
- }
+ if (IsOwner(steamID)) {
+ return "Couldn't find any bot named " + botName + "!";
}
- if (gamesToPlay.Count == 0) {
- return "List of games is empty!";
- }
-
- return await bot.ResponsePlay(steamID, gamesToPlay).ConfigureAwait(false);
+ return null;
}
private string ResponseStart(ulong steamID) {
@@ -1691,6 +2173,100 @@ namespace ArchiSteamFarm {
return null;
}
+ private static string ResponseStartAll(ulong steamID) {
+ if (steamID == 0) {
+ ASF.ArchiLogger.LogNullError(nameof(steamID));
+ return null;
+ }
+
+ if (!IsOwner(steamID)) {
+ return null;
+ }
+
+ foreach (Bot bot in Bots.Values.Where(bot => !bot.KeepRunning)) {
+ bot.ResponseStart(steamID);
+ }
+
+ return "Done!";
+ }
+
+ private string ResponseStatus(ulong steamID) {
+ if (steamID == 0) {
+ ArchiLogger.LogNullError(nameof(steamID));
+ return null;
+ }
+
+ if (!IsMaster(steamID)) {
+ return null;
+ }
+
+ if (!IsConnectedAndLoggedOn) {
+ if (KeepRunning) {
+ return "Bot " + BotName + " is not connected.";
+ }
+
+ return "Bot " + BotName + " is not running.";
+ }
+
+ if (PlayingBlocked) {
+ return "Bot " + BotName + " is currently being used.";
+ }
+
+ if (CardsFarmer.Paused) {
+ return "Bot " + BotName + " is paused or running in manual mode.";
+ }
+
+ if (CardsFarmer.CurrentGamesFarming.Count == 0) {
+ return "Bot " + BotName + " is not farming anything.";
+ }
+
+ StringBuilder response = new StringBuilder("Bot " + BotName + " is farming ");
+
+ if (CardsFarmer.CurrentGamesFarming.Count == 1) {
+ CardsFarmer.Game game = CardsFarmer.CurrentGamesFarming.First();
+ response.Append("game " + game.AppID + " (" + game.GameName + ", " + game.CardsRemaining + " card drops remaining)");
+ } else {
+ response.Append("appIDs " + string.Join(", ", CardsFarmer.CurrentGamesFarming.Select(game => game.AppID)));
+ }
+
+ response.Append(" and has a total of " + CardsFarmer.GamesToFarm.Count + " games (" + CardsFarmer.GamesToFarm.Sum(game => game.CardsRemaining) + " cards) left to farm.");
+ return response.ToString();
+ }
+
+ private static string ResponseStatus(ulong steamID, string botName) {
+ if ((steamID == 0) || string.IsNullOrEmpty(botName)) {
+ ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botName));
+ return null;
+ }
+
+ Bot bot;
+ if (Bots.TryGetValue(botName, out bot)) {
+ return bot.ResponseStatus(steamID);
+ }
+
+ if (IsOwner(steamID)) {
+ return "Couldn't find any bot named " + botName + "!";
+ }
+
+ return null;
+ }
+
+ private static string ResponseStatusAll(ulong steamID) {
+ if (steamID == 0) {
+ ASF.ArchiLogger.LogNullError(nameof(steamID));
+ return null;
+ }
+
+ if (!IsOwner(steamID)) {
+ return null;
+ }
+
+ HashSet botsRunning = new HashSet(Bots.Where(bot => bot.Value.KeepRunning).OrderBy(bot => bot.Key).Select(bot => bot.Value));
+ IEnumerable statuses = botsRunning.Select(bot => bot.ResponseStatus(steamID));
+
+ return Environment.NewLine + string.Join(Environment.NewLine, statuses) + Environment.NewLine + "There are " + botsRunning.Count + "/" + Bots.Count + " bots running, with total of " + botsRunning.Sum(bot => bot.CardsFarmer.GamesToFarm.Count) + " games (" + botsRunning.Sum(bot => bot.CardsFarmer.GamesToFarm.Sum(game => game.CardsRemaining)) + " cards) left to farm.";
+ }
+
private string ResponseStop(ulong steamID) {
if (steamID == 0) {
ArchiLogger.LogNullError(nameof(steamID));
@@ -1763,37 +2339,6 @@ namespace ArchiSteamFarm {
return "ASF V" + SharedInfo.Version;
}
- private void HandleCallbacks() {
- TimeSpan timeSpan = TimeSpan.FromMilliseconds(CallbackSleep);
- while (KeepRunning || SteamClient.IsConnected) {
- if (!CallbackSemaphore.Wait(0)) {
- return;
- }
-
- try {
- CallbackManager.RunWaitCallbacks(timeSpan);
- } catch (Exception e) {
- ArchiLogger.LogGenericException(e);
- } finally {
- CallbackSemaphore.Release();
- }
- }
- }
-
- private async Task HandleMessage(ulong chatID, ulong steamID, string message) {
- if ((chatID == 0) || (steamID == 0) || string.IsNullOrEmpty(message)) {
- ArchiLogger.LogNullError(nameof(chatID) + " || " + nameof(steamID) + " || " + nameof(message));
- return;
- }
-
- string response = await Response(steamID, message).ConfigureAwait(false);
- if (string.IsNullOrEmpty(response)) { // We respond with null when user is not authorized (and similar)
- return;
- }
-
- SendMessage(chatID, response);
- }
-
private void SendMessage(ulong steamID, string message) {
if ((steamID == 0) || string.IsNullOrEmpty(message)) {
ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(message));
@@ -1839,626 +2384,45 @@ namespace ArchiSteamFarm {
}
}
- private void JoinMasterChat() {
- if (!IsConnectedAndLoggedOn || (BotConfig.SteamMasterClanID == 0)) {
- return;
- }
-
- SteamFriends.JoinChat(BotConfig.SteamMasterClanID);
- }
-
- private bool InitializeLoginAndPassword(bool requiresPassword) {
- if (string.IsNullOrEmpty(BotConfig.SteamLogin)) {
- BotConfig.SteamLogin = Program.GetUserInput(ASF.EUserInputType.Login, BotName);
- if (string.IsNullOrEmpty(BotConfig.SteamLogin)) {
- return false;
- }
- }
-
- if (!string.IsNullOrEmpty(BotConfig.SteamPassword) || (!requiresPassword && !string.IsNullOrEmpty(BotDatabase.LoginKey))) {
- return true;
- }
-
- BotConfig.SteamPassword = Program.GetUserInput(ASF.EUserInputType.Password, BotName);
- return !string.IsNullOrEmpty(BotConfig.SteamPassword);
- }
-
- private void ResetGamesPlayed() {
- if (PlayingBlocked) {
- return;
- }
-
- ArchiHandler.PlayGames(BotConfig.GamesPlayedWhileIdle, BotConfig.CustomGamePlayedWhileIdle);
- }
-
- private void OnConnected(SteamClient.ConnectedCallback callback) {
- if (callback == null) {
- ArchiLogger.LogNullError(nameof(callback));
- return;
- }
-
- if (callback.Result != EResult.OK) {
- ArchiLogger.LogGenericError("Unable to connect to Steam: " + callback.Result);
- return;
- }
-
- ArchiLogger.LogGenericInfo("Connected to Steam!");
-
+ private async Task Start() {
if (!KeepRunning) {
- ArchiLogger.LogGenericInfo("Disconnecting...");
- Disconnect();
- return;
+ KeepRunning = true;
+ Task.Run(() => HandleCallbacks()).Forget();
+ ArchiLogger.LogGenericInfo("Starting...");
}
- byte[] sentryFileHash = null;
- if (File.Exists(SentryFile)) {
- try {
- byte[] sentryFileContent = File.ReadAllBytes(SentryFile);
- sentryFileHash = SteamKit2.CryptoHelper.SHAHash(sentryFileContent);
- } catch (Exception e) {
- ArchiLogger.LogGenericException(e);
- }
- }
-
- if (!InitializeLoginAndPassword(false)) {
- Stop();
- return;
- }
-
- ArchiLogger.LogGenericInfo("Logging in...");
-
- string password = BotConfig.SteamPassword;
- if (!string.IsNullOrEmpty(password)) {
- // Steam silently ignores non-ASCII characters in password, we're going to do the same
- // Don't ask me why, I know it's stupid
- password = Regex.Replace(password, @"[^\u0000-\u007F]+", "");
- }
-
- // Decrypt login key if needed
- string loginKey = BotDatabase.LoginKey;
- if (!string.IsNullOrEmpty(loginKey) && (loginKey.Length > 19)) {
- loginKey = CryptoHelper.Decrypt(BotConfig.PasswordFormat, loginKey);
- }
-
- SteamUser.LogOnDetails logOnDetails = new SteamUser.LogOnDetails {
- Username = BotConfig.SteamLogin,
- Password = password,
- AuthCode = AuthCode,
- CellID = Program.GlobalDatabase.CellID,
- LoginID = LoginID,
- LoginKey = loginKey,
- TwoFactorCode = TwoFactorCode,
- SentryFileHash = sentryFileHash,
- ShouldRememberPassword = true
- };
-
- try {
- SteamUser.LogOn(logOnDetails);
- } catch {
- // TODO: Remove me once https://github.com/SteamRE/SteamKit/issues/305 is fixed
- ArchiHandler.LogOnWithoutMachineID(logOnDetails);
- }
- }
-
- private async void OnDisconnected(SteamClient.DisconnectedCallback callback) {
- if (callback == null) {
- ArchiLogger.LogNullError(nameof(callback));
- return;
- }
-
- HeartBeatFailures = 0;
-
- EResult lastLogOnResult = LastLogOnResult;
- LastLogOnResult = EResult.Invalid;
-
- ArchiLogger.LogGenericInfo("Disconnected from Steam!");
-
- ArchiWebHandler.OnDisconnected();
- CardsFarmer.OnDisconnected();
- Trading.OnDisconnected();
-
- FirstTradeSent = false;
- HandledGifts.ClearAndTrim();
-
- // If we initiated disconnect, do not attempt to reconnect
- if (callback.UserInitiated) {
- return;
- }
-
- switch (lastLogOnResult) {
- case EResult.Invalid:
- // Invalid means that we didn't get OnLoggedOn() in the first place, so Steam is down
- // Always reset one-time-only access tokens in this case, as OnLoggedOn() didn't do that for us
- AuthCode = TwoFactorCode = null;
- break;
- case EResult.InvalidPassword:
- // If we didn't use login key, it's nearly always rate limiting
- if (string.IsNullOrEmpty(BotDatabase.LoginKey)) {
- goto case EResult.RateLimitExceeded;
- }
-
- BotDatabase.LoginKey = null;
- ArchiLogger.LogGenericInfo("Removed expired login key");
- break;
- case EResult.NoConnection:
- case EResult.ServiceUnavailable:
- case EResult.Timeout:
- case EResult.TryAnotherCM:
- await Task.Delay(5000).ConfigureAwait(false);
- break;
- case EResult.RateLimitExceeded:
- ArchiLogger.LogGenericInfo("Will retry after 25 minutes...");
- await Task.Delay(25 * 60 * 1000).ConfigureAwait(false); // Captcha disappears after around 20 minutes, so we make it 25
- break;
- }
-
- if (!KeepRunning || SteamClient.IsConnected) {
- return;
- }
-
- ArchiLogger.LogGenericInfo("Reconnecting...");
await Connect().ConfigureAwait(false);
}
- private void OnFreeLicense(SteamApps.FreeLicenseCallback callback) {
- if (callback == null) {
- ArchiLogger.LogNullError(nameof(callback));
+ private void StartFamilySharingInactivityTimer() {
+ if (FamilySharingInactivityTimer != null) {
+ return;
}
+
+ FamilySharingInactivityTimer = new Timer(e => CheckFamilySharingInactivity(), null, TimeSpan.FromMinutes(FamilySharingInactivityMinutes), // Delay
+ Timeout.InfiniteTimeSpan // Period
+ );
}
- private async void OnGuestPassList(SteamApps.GuestPassListCallback callback) {
- if (callback?.GuestPasses == null) {
- ArchiLogger.LogNullError(nameof(callback) + " || " + nameof(callback.GuestPasses));
+ private void StopFamilySharingInactivityTimer() {
+ if (FamilySharingInactivityTimer == null) {
return;
}
- if ((callback.CountGuestPassesToRedeem == 0) || (callback.GuestPasses.Count == 0) || !BotConfig.AcceptGifts) {
- return;
- }
-
- foreach (ulong gid in callback.GuestPasses.Select(guestPass => guestPass["gid"].AsUnsignedLong()).Where(gid => (gid != 0) && !HandledGifts.Contains(gid))) {
- HandledGifts.Add(gid);
-
- ArchiLogger.LogGenericInfo("Accepting gift: " + gid + "...");
- await LimitGiftsRequestsAsync().ConfigureAwait(false);
-
- ArchiHandler.RedeemGuestPassResponseCallback response = await ArchiHandler.RedeemGuestPass(gid).ConfigureAwait(false);
- if (response != null) {
- if (response.Result == EResult.OK) {
- ArchiLogger.LogGenericInfo("Success!");
- } else {
- ArchiLogger.LogGenericWarning("Failed with error: " + response.Result);
- }
- } else {
- ArchiLogger.LogGenericWarning("Failed!");
- }
- }
+ FamilySharingInactivityTimer.Change(Timeout.Infinite, Timeout.Infinite);
+ FamilySharingInactivityTimer.Dispose();
+ FamilySharingInactivityTimer = null;
}
- private async void OnLicenseList(SteamApps.LicenseListCallback callback) {
- if (callback?.LicenseList == null) {
- ArchiLogger.LogNullError(nameof(callback) + " || " + nameof(callback.LicenseList));
- return;
- }
-
- OwnedPackageIDs.Clear();
-
- foreach (SteamApps.LicenseListCallback.License license in callback.LicenseList) {
- OwnedPackageIDs.Add(license.PackageID);
- }
-
- OwnedPackageIDs.TrimExcess();
-
- await Task.Delay(1000).ConfigureAwait(false); // Wait a second for eventual PlayingSessionStateCallback
-
- if (!ArchiWebHandler.Ready) {
- for (byte i = 0; (i < Program.GlobalConfig.HttpTimeout) && !ArchiWebHandler.Ready; i++) {
- await Task.Delay(1000).ConfigureAwait(false);
- }
-
- if (!ArchiWebHandler.Ready) {
- return;
- }
- }
-
- await CardsFarmer.OnNewGameAdded().ConfigureAwait(false);
- }
-
- private void OnChatInvite(SteamFriends.ChatInviteCallback callback) {
- if ((callback?.ChatRoomID == null) || (callback.PatronID == null)) {
- ArchiLogger.LogNullError(nameof(callback) + " || " + nameof(callback.ChatRoomID) + " || " + nameof(callback.PatronID));
- return;
- }
-
- if (!IsMaster(callback.PatronID)) {
- return;
- }
-
- SteamFriends.JoinChat(callback.ChatRoomID);
- }
-
- private async void OnChatMsg(SteamFriends.ChatMsgCallback callback) {
- if (callback == null) {
- ArchiLogger.LogNullError(nameof(callback));
- return;
- }
-
- if (callback.ChatMsgType != EChatEntryType.ChatMsg) {
- return;
- }
-
- if ((callback.ChatRoomID == null) || (callback.ChatterID == null) || string.IsNullOrEmpty(callback.Message)) {
- ArchiLogger.LogNullError(nameof(callback.ChatRoomID) + " || " + nameof(callback.ChatterID) + " || " + nameof(callback.Message));
- return;
- }
-
- ArchiLogger.LogGenericTrace(callback.ChatRoomID.ConvertToUInt64() + "/" + callback.ChatterID.ConvertToUInt64() + ": " + callback.Message);
-
- switch (callback.Message) {
- case "!leave":
- if (!IsMaster(callback.ChatterID)) {
- break;
- }
-
- SteamFriends.LeaveChat(callback.ChatRoomID);
- break;
- default:
- await HandleMessage(callback.ChatRoomID, callback.ChatterID, callback.Message).ConfigureAwait(false);
- break;
- }
- }
-
- private void OnFriendsList(SteamFriends.FriendsListCallback callback) {
- if (callback?.FriendList == null) {
- ArchiLogger.LogNullError(nameof(callback) + " || " + nameof(callback.FriendList));
- return;
- }
-
- foreach (SteamFriends.FriendsListCallback.Friend friend in callback.FriendList.Where(friend => friend.Relationship == EFriendRelationship.RequestRecipient)) {
- if (IsMaster(friend.SteamID)) {
- SteamFriends.AddFriend(friend.SteamID);
- } else if (BotConfig.IsBotAccount) {
- SteamFriends.RemoveFriend(friend.SteamID);
- }
- }
- }
-
- private async void OnFriendMsg(SteamFriends.FriendMsgCallback callback) {
- if (callback == null) {
- ArchiLogger.LogNullError(nameof(callback));
- return;
- }
-
- if (callback.EntryType != EChatEntryType.ChatMsg) {
- return;
- }
-
- if ((callback.Sender == null) || string.IsNullOrEmpty(callback.Message)) {
- ArchiLogger.LogNullError(nameof(callback.Sender) + " || " + nameof(callback.Message));
- return;
- }
-
- ArchiLogger.LogGenericTrace(callback.Sender.ConvertToUInt64() + ": " + callback.Message);
-
- await HandleMessage(callback.Sender, callback.Sender, callback.Message).ConfigureAwait(false);
- }
-
- private async void OnFriendMsgHistory(SteamFriends.FriendMsgHistoryCallback callback) {
- if ((callback?.Messages == null) || (callback.SteamID == null)) {
- ArchiLogger.LogNullError(nameof(callback) + " || " + nameof(callback.Messages) + " || " + nameof(callback.SteamID));
- return;
- }
-
- if ((callback.Messages.Count == 0) || !IsMaster(callback.SteamID)) {
- return;
- }
-
- // Get last message
- SteamFriends.FriendMsgHistoryCallback.FriendMessage lastMessage = callback.Messages[callback.Messages.Count - 1];
-
- // If message is read already, return
- if (!lastMessage.Unread) {
- return;
- }
-
- // If message is too old, return
- if (DateTime.UtcNow.Subtract(lastMessage.Timestamp).TotalHours > 1) {
- return;
- }
-
- // Handle the message
- await HandleMessage(callback.SteamID, callback.SteamID, lastMessage.Message).ConfigureAwait(false);
- }
-
- private void OnPersonaState(SteamFriends.PersonaStateCallback callback) {
- if (callback == null) {
- ArchiLogger.LogNullError(nameof(callback));
- return;
- }
-
- if (callback.FriendID == SteamClient.SteamID) {
- Events.OnStateUpdated(this, callback);
- } else if ((callback.FriendID == LibraryLockedBySteamID) && (callback.GameID == 0)) {
- LibraryLockedBySteamID = 0;
- CheckOccupationStatus();
- }
- }
-
- private void OnAccountInfo(SteamUser.AccountInfoCallback callback) {
- if (callback == null) {
- ArchiLogger.LogNullError(nameof(callback));
- return;
- }
-
- if (!BotConfig.FarmOffline) {
- SteamFriends.SetPersonaState(EPersonaState.Online);
- }
- }
-
- private void OnLoggedOff(SteamUser.LoggedOffCallback callback) {
- if (callback == null) {
- ArchiLogger.LogNullError(nameof(callback));
- return;
- }
-
- ArchiLogger.LogGenericInfo("Logged off of Steam: " + callback.Result);
-
- switch (callback.Result) {
- case EResult.LogonSessionReplaced:
- ArchiLogger.LogGenericError("This account seems to be used in another ASF instance, which is undefined behaviour, refusing to keep it running!");
- Stop();
- break;
- }
- }
-
- private async void OnLoggedOn(SteamUser.LoggedOnCallback callback) {
- if (callback == null) {
- ArchiLogger.LogNullError(nameof(callback));
- return;
- }
-
- // Always reset one-time-only access tokens
- AuthCode = TwoFactorCode = null;
-
- // Keep LastLogOnResult for OnDisconnected()
- LastLogOnResult = callback.Result;
-
- switch (callback.Result) {
- case EResult.AccountLogonDenied:
- AuthCode = Program.GetUserInput(ASF.EUserInputType.SteamGuard, BotName);
- if (string.IsNullOrEmpty(AuthCode)) {
- Stop();
- }
-
- break;
- case EResult.AccountLoginDeniedNeedTwoFactor:
- if (BotDatabase.MobileAuthenticator == null) {
- TwoFactorCode = Program.GetUserInput(ASF.EUserInputType.TwoFactorAuthentication, BotName);
- if (string.IsNullOrEmpty(TwoFactorCode)) {
- Stop();
- }
- } else {
- ArchiLogger.LogGenericWarning("2FA code was invalid despite of using ASF 2FA. Invalid authenticator or bad timing?");
- }
-
- break;
- case EResult.OK:
- ArchiLogger.LogGenericInfo("Successfully logged on!");
-
- // Old status for these doesn't matter, we'll update them if needed
- LibraryLockedBySteamID = 0;
- IsLimitedUser = PlayingBlocked = false;
-
- if (callback.AccountFlags.HasFlag(EAccountFlags.LimitedUser)) {
- IsLimitedUser = true;
- ArchiLogger.LogGenericWarning("This account is limited, farming process is permanently unavailable until the restriction is removed!");
- }
-
- if ((callback.CellID != 0) && (Program.GlobalDatabase.CellID != callback.CellID)) {
- Program.GlobalDatabase.CellID = callback.CellID;
- }
-
- if (BotDatabase.MobileAuthenticator == null) {
- // Support and convert SDA files
- string maFilePath = Path.Combine(SharedInfo.ConfigDirectory, callback.ClientSteamID.ConvertToUInt64() + ".maFile");
- if (File.Exists(maFilePath)) {
- ImportAuthenticator(maFilePath);
- }
- }
-
- if (string.IsNullOrEmpty(BotConfig.SteamParentalPIN)) {
- BotConfig.SteamParentalPIN = Program.GetUserInput(ASF.EUserInputType.SteamParentalPIN, BotName);
- if (string.IsNullOrEmpty(BotConfig.SteamParentalPIN)) {
- Stop();
- return;
- }
- }
-
- if (!await ArchiWebHandler.Init(callback.ClientSteamID, SteamClient.ConnectedUniverse, callback.WebAPIUserNonce, BotConfig.SteamParentalPIN).ConfigureAwait(false)) {
- if (!await RefreshSession().ConfigureAwait(false)) {
- return;
- }
- }
-
- InitializeFamilySharing().Forget();
-
- if (BotConfig.DismissInventoryNotifications) {
- ArchiWebHandler.MarkInventory().Forget();
- }
-
- if (BotConfig.SteamMasterClanID != 0) {
- Task.Run(async () => {
- await ArchiWebHandler.JoinGroup(BotConfig.SteamMasterClanID).ConfigureAwait(false);
- JoinMasterChat();
- }).Forget();
- }
-
- if (Program.GlobalConfig.Statistics) {
- ArchiWebHandler.JoinGroup(SharedInfo.ASFGroupSteamID).Forget();
- }
-
- Trading.CheckTrades().Forget();
- break;
- case EResult.InvalidPassword:
- case EResult.NoConnection:
- case EResult.RateLimitExceeded:
- case EResult.ServiceUnavailable:
- case EResult.Timeout:
- case EResult.TryAnotherCM:
- case EResult.TwoFactorCodeMismatch:
- ArchiLogger.LogGenericWarning("Unable to login to Steam: " + callback.Result + " / " + callback.ExtendedResult);
- break;
- default: // Unexpected result, shutdown immediately
- ArchiLogger.LogGenericError("Unable to login to Steam: " + callback.Result + " / " + callback.ExtendedResult);
- Stop();
- break;
- }
- }
-
- private void OnLoginKey(SteamUser.LoginKeyCallback callback) {
- if (string.IsNullOrEmpty(callback?.LoginKey)) {
- ArchiLogger.LogNullError(nameof(callback) + " || " + nameof(callback.LoginKey));
- return;
- }
-
- string loginKey = callback.LoginKey;
- if (!string.IsNullOrEmpty(loginKey)) {
- loginKey = CryptoHelper.Encrypt(BotConfig.PasswordFormat, loginKey);
- }
-
- BotDatabase.LoginKey = loginKey;
- SteamUser.AcceptNewLoginKey(callback);
- }
-
- private void OnMachineAuth(SteamUser.UpdateMachineAuthCallback callback) {
- if (callback == null) {
- ArchiLogger.LogNullError(nameof(callback));
- return;
- }
-
- int fileSize;
- byte[] sentryHash;
-
- try {
- using (FileStream fileStream = File.Open(SentryFile, FileMode.OpenOrCreate, FileAccess.ReadWrite)) {
- fileStream.Seek(callback.Offset, SeekOrigin.Begin);
- fileStream.Write(callback.Data, 0, callback.BytesToWrite);
- fileSize = (int) fileStream.Length;
-
- fileStream.Seek(0, SeekOrigin.Begin);
- using (SHA1CryptoServiceProvider sha = new SHA1CryptoServiceProvider()) {
- sentryHash = sha.ComputeHash(fileStream);
- }
- }
- } catch (Exception e) {
- ArchiLogger.LogGenericException(e);
- return;
- }
-
- // Inform the steam servers that we're accepting this sentry file
- SteamUser.SendMachineAuthResponse(new SteamUser.MachineAuthDetails {
- JobID = callback.JobID,
- FileName = callback.FileName,
- BytesWritten = callback.BytesToWrite,
- FileSize = fileSize,
- Offset = callback.Offset,
- Result = EResult.OK,
- LastError = 0,
- OneTimePassword = callback.OneTimePassword,
- SentryFileHash = sentryHash
- });
- }
-
- private void OnWebAPIUserNonce(SteamUser.WebAPIUserNonceCallback callback) {
- if (callback == null) {
- ArchiLogger.LogNullError(nameof(callback));
- }
- }
-
- private void OnNotifications(ArchiHandler.NotificationsCallback callback) {
- if (callback == null) {
- ArchiLogger.LogNullError(nameof(callback));
- return;
- }
-
- if ((callback.Notifications == null) || (callback.Notifications.Count == 0)) {
- return;
- }
-
- foreach (ArchiHandler.NotificationsCallback.ENotification notification in callback.Notifications) {
- switch (notification) {
- case ArchiHandler.NotificationsCallback.ENotification.Items:
- CardsFarmer.OnNewItemsNotification().Forget();
- if (BotConfig.DismissInventoryNotifications) {
- ArchiWebHandler.MarkInventory().Forget();
- }
- break;
- case ArchiHandler.NotificationsCallback.ENotification.Trading:
- Trading.CheckTrades().Forget();
- break;
- }
- }
- }
-
- private void OnOfflineMessage(ArchiHandler.OfflineMessageCallback callback) {
- if (callback == null) {
- ArchiLogger.LogNullError(nameof(callback));
- return;
- }
-
- if ((callback.OfflineMessagesCount == 0) || !BotConfig.HandleOfflineMessages) {
- return;
- }
-
- SteamFriends.RequestOfflineMessages();
- }
-
- private void OnPlayingSessionState(ArchiHandler.PlayingSessionStateCallback callback) {
- if (callback == null) {
- ArchiLogger.LogNullError(nameof(callback));
- return;
- }
-
- if (callback.PlayingBlocked == PlayingBlocked) {
- return; // No status update, we're not interested
- }
-
- PlayingBlocked = callback.PlayingBlocked;
- CheckOccupationStatus();
- }
-
- private void OnPurchaseResponse(ArchiHandler.PurchaseResponseCallback callback) {
- if (callback == null) {
- ArchiLogger.LogNullError(nameof(callback));
- }
- }
-
- private void OnSharedLibraryLockStatus(ArchiHandler.SharedLibraryLockStatusCallback callback) {
- if (callback == null) {
- ArchiLogger.LogNullError(nameof(callback));
- return;
- }
-
- // Ignore no status updates
- if (LibraryLockedBySteamID == 0) {
- if ((callback.LibraryLockedBySteamID == 0) || (callback.LibraryLockedBySteamID == SteamClient.SteamID)) {
- return;
- }
-
- LibraryLockedBySteamID = callback.LibraryLockedBySteamID;
- } else {
- if ((callback.LibraryLockedBySteamID != 0) && (callback.LibraryLockedBySteamID != SteamClient.SteamID)) {
- return;
- }
-
- if (SteamFriends.GetFriendGamePlayed(LibraryLockedBySteamID) != 0) {
- return;
- }
-
- LibraryLockedBySteamID = 0;
- }
-
- CheckOccupationStatus();
+ [Flags]
+ private enum ERedeemFlags : byte {
+ None = 0,
+ Validate = 1,
+ ForceForwarding = 2,
+ SkipForwarding = 4,
+ ForceDistribution = 8,
+ SkipDistribution = 16,
+ SkipInitial = 32
}
}
-}
+}
\ No newline at end of file
diff --git a/ArchiSteamFarm/BotConfig.cs b/ArchiSteamFarm/BotConfig.cs
index fcc774e8d..ee8fdee11 100644
--- a/ArchiSteamFarm/BotConfig.cs
+++ b/ArchiSteamFarm/BotConfig.cs
@@ -22,12 +22,12 @@
*/
-using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
+using Newtonsoft.Json;
namespace ArchiSteamFarm {
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
@@ -35,98 +35,14 @@ namespace ArchiSteamFarm {
[SuppressMessage("ReSharper", "ConvertToConstant.Local")]
[SuppressMessage("ReSharper", "ConvertToConstant.Global")]
internal sealed class BotConfig {
- internal enum EFarmingOrder : byte {
- Unordered,
- AppIDsAscending,
- AppIDsDescending,
- CardDropsAscending,
- CardDropsDescending,
- HoursAscending,
- HoursDescending,
- NamesAscending,
- NamesDescending
- }
-
- [Flags]
- internal enum ETradingPreferences : byte {
- [SuppressMessage("ReSharper", "UnusedMember.Global")]
- None = 0,
- AcceptDonations = 1,
- SteamTradeMatcher = 2,
- MatchEverything = 4
- }
-
[JsonProperty(Required = Required.DisallowNull)]
- internal readonly bool Enabled = false;
-
- [JsonProperty(Required = Required.DisallowNull)]
- internal readonly bool Paused = false;
-
- [JsonProperty]
- internal string SteamLogin { get; set; }
-
- [JsonProperty]
- internal string SteamPassword { get; set; }
-
- [JsonProperty(Required = Required.DisallowNull)]
- internal readonly CryptoHelper.ECryptoMethod PasswordFormat = CryptoHelper.ECryptoMethod.PlainText;
-
- [JsonProperty]
- internal string SteamParentalPIN { get; set; } = "0";
-
- [JsonProperty]
- internal readonly string SteamApiKey = null;
-
- [JsonProperty(Required = Required.DisallowNull)]
- internal readonly ulong SteamMasterID = 0;
-
- [JsonProperty(Required = Required.DisallowNull)]
- internal readonly ulong SteamMasterClanID = 0;
-
- [JsonProperty(Required = Required.DisallowNull)]
- internal readonly bool CardDropsRestricted = true;
-
- [JsonProperty(Required = Required.DisallowNull)]
- internal readonly bool DismissInventoryNotifications = true;
-
- [JsonProperty(Required = Required.DisallowNull)]
- internal readonly EFarmingOrder FarmingOrder = EFarmingOrder.Unordered;
-
- [JsonProperty(Required = Required.DisallowNull)]
- internal readonly bool FarmOffline = false;
-
- [JsonProperty(Required = Required.DisallowNull)]
- internal readonly bool HandleOfflineMessages = false;
+ internal readonly byte AcceptConfirmationsPeriod = 0;
[JsonProperty(Required = Required.DisallowNull)]
internal readonly bool AcceptGifts = false;
[JsonProperty(Required = Required.DisallowNull)]
- internal readonly bool IsBotAccount = false;
-
- [JsonProperty(Required = Required.DisallowNull)]
- internal readonly bool ForwardKeysToOtherBots = false;
-
- [JsonProperty(Required = Required.DisallowNull)]
- internal readonly bool DistributeKeys = false;
-
- [JsonProperty(Required = Required.DisallowNull)]
- internal readonly bool ShutdownOnFarmingFinished = false;
-
- [JsonProperty(Required = Required.DisallowNull)]
- internal readonly bool SendOnFarmingFinished = false;
-
- [JsonProperty]
- internal readonly string SteamTradeToken = null;
-
- [JsonProperty(Required = Required.DisallowNull)]
- internal readonly byte SendTradePeriod = 0;
-
- [JsonProperty(Required = Required.DisallowNull)]
- internal readonly ETradingPreferences TradingPreferences = ETradingPreferences.AcceptDonations;
-
- [JsonProperty(Required = Required.DisallowNull)]
- internal readonly byte AcceptConfirmationsPeriod = 0;
+ internal readonly bool CardDropsRestricted = true;
[JsonProperty]
internal readonly string CustomGamePlayedWhileFarming = null;
@@ -134,9 +50,75 @@ namespace ArchiSteamFarm {
[JsonProperty]
internal readonly string CustomGamePlayedWhileIdle = null;
+ [JsonProperty(Required = Required.DisallowNull)]
+ internal readonly bool DismissInventoryNotifications = true;
+
+ [JsonProperty(Required = Required.DisallowNull)]
+ internal readonly bool DistributeKeys = false;
+
+ [JsonProperty(Required = Required.DisallowNull)]
+ internal readonly bool Enabled = false;
+
+ [JsonProperty(Required = Required.DisallowNull)]
+ internal readonly EFarmingOrder FarmingOrder = EFarmingOrder.Unordered;
+
+ [JsonProperty(Required = Required.DisallowNull)]
+ internal readonly bool FarmOffline = false;
+
+ [JsonProperty(Required = Required.DisallowNull)]
+ internal readonly bool ForwardKeysToOtherBots = false;
+
[JsonProperty(Required = Required.DisallowNull)]
internal readonly HashSet GamesPlayedWhileIdle = new HashSet();
+ [JsonProperty(Required = Required.DisallowNull)]
+ internal readonly bool HandleOfflineMessages = false;
+
+ [JsonProperty(Required = Required.DisallowNull)]
+ internal readonly bool IsBotAccount = false;
+
+ [JsonProperty(Required = Required.DisallowNull)]
+ internal readonly CryptoHelper.ECryptoMethod PasswordFormat = CryptoHelper.ECryptoMethod.PlainText;
+
+ [JsonProperty(Required = Required.DisallowNull)]
+ internal readonly bool Paused = false;
+
+ [JsonProperty(Required = Required.DisallowNull)]
+ internal readonly bool SendOnFarmingFinished = false;
+
+ [JsonProperty(Required = Required.DisallowNull)]
+ internal readonly byte SendTradePeriod = 0;
+
+ [JsonProperty(Required = Required.DisallowNull)]
+ internal readonly bool ShutdownOnFarmingFinished = false;
+
+ [JsonProperty]
+ internal readonly string SteamApiKey = null;
+
+ [JsonProperty(Required = Required.DisallowNull)]
+ internal readonly ulong SteamMasterClanID = 0;
+
+ [JsonProperty(Required = Required.DisallowNull)]
+ internal readonly ulong SteamMasterID = 0;
+
+ [JsonProperty]
+ internal readonly string SteamTradeToken = null;
+
+ [JsonProperty(Required = Required.DisallowNull)]
+ internal readonly ETradingPreferences TradingPreferences = ETradingPreferences.AcceptDonations;
+
+ [JsonProperty]
+ internal string SteamLogin { get; set; }
+
+ [JsonProperty]
+ internal string SteamParentalPIN { get; set; } = "0";
+
+ [JsonProperty]
+ internal string SteamPassword { get; set; }
+
+ // This constructor is used only by deserializer
+ private BotConfig() { }
+
internal static BotConfig Load(string filePath) {
if (string.IsNullOrEmpty(filePath)) {
ASF.ArchiLogger.LogNullError(nameof(filePath));
@@ -182,7 +164,25 @@ namespace ArchiSteamFarm {
return botConfig;
}
- // This constructor is used only by deserializer
- private BotConfig() { }
+ internal enum EFarmingOrder : byte {
+ Unordered,
+ AppIDsAscending,
+ AppIDsDescending,
+ CardDropsAscending,
+ CardDropsDescending,
+ HoursAscending,
+ HoursDescending,
+ NamesAscending,
+ NamesDescending
+ }
+
+ [Flags]
+ internal enum ETradingPreferences : byte {
+ [SuppressMessage("ReSharper", "UnusedMember.Global")]
+ None = 0,
+ AcceptDonations = 1,
+ SteamTradeMatcher = 2,
+ MatchEverything = 4
+ }
}
-}
+}
\ No newline at end of file
diff --git a/ArchiSteamFarm/BotDatabase.cs b/ArchiSteamFarm/BotDatabase.cs
index a9c642a9a..1eec758f4 100644
--- a/ArchiSteamFarm/BotDatabase.cs
+++ b/ArchiSteamFarm/BotDatabase.cs
@@ -22,21 +22,18 @@
*/
-using Newtonsoft.Json;
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Threading;
+using Newtonsoft.Json;
namespace ArchiSteamFarm {
internal sealed class BotDatabase {
- [JsonProperty]
- private string _LoginKey;
+ private readonly object FileLock = new object();
internal string LoginKey {
- get {
- return _LoginKey;
- }
+ get { return _LoginKey; }
set {
if (_LoginKey == value) {
@@ -48,13 +45,8 @@ namespace ArchiSteamFarm {
}
}
- [JsonProperty]
- private MobileAuthenticator _MobileAuthenticator;
-
internal MobileAuthenticator MobileAuthenticator {
- get {
- return _MobileAuthenticator;
- }
+ get { return _MobileAuthenticator; }
set {
if (_MobileAuthenticator == value) {
@@ -66,10 +58,28 @@ namespace ArchiSteamFarm {
}
}
- private readonly object FileLock = new object();
+ [JsonProperty]
+ private string _LoginKey;
+
+ [JsonProperty]
+ private MobileAuthenticator _MobileAuthenticator;
private string FilePath;
+ // This constructor is used when creating new database
+ private BotDatabase(string filePath) {
+ if (string.IsNullOrEmpty(filePath)) {
+ throw new ArgumentNullException(nameof(filePath));
+ }
+
+ FilePath = filePath;
+ Save();
+ }
+
+ // This constructor is used only by deserializer
+ [SuppressMessage("ReSharper", "UnusedMember.Local")]
+ private BotDatabase() { }
+
internal static BotDatabase Load(string filePath) {
if (string.IsNullOrEmpty(filePath)) {
ASF.ArchiLogger.LogNullError(nameof(filePath));
@@ -98,20 +108,6 @@ namespace ArchiSteamFarm {
return botDatabase;
}
- // This constructor is used when creating new database
- private BotDatabase(string filePath) {
- if (string.IsNullOrEmpty(filePath)) {
- throw new ArgumentNullException(nameof(filePath));
- }
-
- FilePath = filePath;
- Save();
- }
-
- // This constructor is used only by deserializer
- [SuppressMessage("ReSharper", "UnusedMember.Local")]
- private BotDatabase() { }
-
internal void Save() {
string json = JsonConvert.SerializeObject(this);
if (string.IsNullOrEmpty(json)) {
@@ -133,4 +129,4 @@ namespace ArchiSteamFarm {
}
}
}
-}
+}
\ No newline at end of file
diff --git a/ArchiSteamFarm/CardsFarmer.cs b/ArchiSteamFarm/CardsFarmer.cs
index 189b4e1ab..0b86fdb70 100755
--- a/ArchiSteamFarm/CardsFarmer.cs
+++ b/ArchiSteamFarm/CardsFarmer.cs
@@ -22,7 +22,6 @@
*/
-using HtmlAgilityPack;
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -30,71 +29,30 @@ using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
+using HtmlAgilityPack;
using Newtonsoft.Json;
namespace ArchiSteamFarm {
internal sealed class CardsFarmer : IDisposable {
- internal sealed class Game {
- [JsonProperty]
- internal readonly uint AppID;
-
- [JsonProperty]
- internal readonly string GameName;
-
- [JsonProperty]
- internal float HoursPlayed { get; set; }
-
- [JsonProperty]
- internal ushort CardsRemaining { get; set; }
-
- //internal string HeaderURL => "https://steamcdn-a.akamaihd.net/steam/apps/" + AppID + "/header.jpg";
-
- internal Game(uint appID, string gameName, float hoursPlayed, ushort cardsRemaining) {
- if ((appID == 0) || string.IsNullOrEmpty(gameName) || (hoursPlayed < 0) || (cardsRemaining == 0)) {
- throw new ArgumentOutOfRangeException(nameof(appID) + " || " + nameof(gameName) + " || " + nameof(hoursPlayed) + " || " + nameof(cardsRemaining));
- }
-
- AppID = appID;
- GameName = gameName;
- HoursPlayed = hoursPlayed;
- CardsRemaining = cardsRemaining;
- }
-
- public override bool Equals(object obj) {
- if (ReferenceEquals(null, obj)) {
- return false;
- }
-
- if (ReferenceEquals(this, obj)) {
- return true;
- }
-
- Game game = obj as Game;
- return (game != null) && Equals(game);
- }
-
- public override int GetHashCode() => (int) AppID;
-
- private bool Equals(Game other) => AppID == other.AppID;
- }
-
internal const byte MaxGamesPlayedConcurrently = 32; // This is limit introduced by Steam Network
- [JsonProperty]
- internal readonly ConcurrentHashSet GamesToFarm = new ConcurrentHashSet();
-
[JsonProperty]
internal readonly ConcurrentHashSet CurrentGamesFarming = new ConcurrentHashSet();
- private readonly ManualResetEventSlim FarmResetEvent = new ManualResetEventSlim(false);
- private readonly SemaphoreSlim FarmingSemaphore = new SemaphoreSlim(1);
+ [JsonProperty]
+ internal readonly ConcurrentHashSet GamesToFarm = new ConcurrentHashSet();
+
private readonly Bot Bot;
+ private readonly SemaphoreSlim FarmingSemaphore = new SemaphoreSlim(1);
+ private readonly ManualResetEventSlim FarmResetEvent = new ManualResetEventSlim(false);
private readonly Timer IdleFarmingTimer;
[JsonProperty]
internal bool Paused { get; private set; }
- private bool KeepFarming, NowFarming, StickyPause;
+ private bool KeepFarming;
+ private bool NowFarming;
+ private bool StickyPause;
internal CardsFarmer(Bot bot) {
if (bot == null) {
@@ -104,16 +62,50 @@ namespace ArchiSteamFarm {
Bot = bot;
if (Program.GlobalConfig.IdleFarmingPeriod > 0) {
- IdleFarmingTimer = new Timer(
- e => CheckGamesForFarming(),
- null,
- TimeSpan.FromHours(Program.GlobalConfig.IdleFarmingPeriod) + TimeSpan.FromMinutes(0.5 * Bot.Bots.Count), // Delay
+ IdleFarmingTimer = new Timer(e => CheckGamesForFarming(), null, TimeSpan.FromHours(Program.GlobalConfig.IdleFarmingPeriod) + TimeSpan.FromMinutes(0.5*Bot.Bots.Count), // Delay
TimeSpan.FromHours(Program.GlobalConfig.IdleFarmingPeriod) // Period
);
}
}
- internal void SetInitialState(bool paused) => StickyPause = Paused = paused;
+ public void Dispose() {
+ // Those are objects that are always being created if constructor doesn't throw exception
+ CurrentGamesFarming.Dispose();
+ GamesToFarm.Dispose();
+ FarmingSemaphore.Dispose();
+ FarmResetEvent.Dispose();
+
+ // Those are objects that might be null and the check should be in-place
+ IdleFarmingTimer?.Dispose();
+ }
+
+ internal void OnDisconnected() => StopFarming().Forget();
+
+ internal async Task OnNewGameAdded() {
+ if (!NowFarming) {
+ // If we're not farming yet, obviously it's worth it to make a check
+ StartFarming().Forget();
+ return;
+ }
+
+ if (Bot.BotConfig.CardDropsRestricted && (GamesToFarm.Count > 0) && (GamesToFarm.Min(game => game.HoursPlayed) < 2)) {
+ // If we have Complex algorithm and some games to boost, it's also worth to make a check
+ // That's because we would check for new games after our current round anyway
+ await StopFarming().ConfigureAwait(false);
+ StartFarming().Forget();
+ }
+ }
+
+ internal async Task OnNewItemsNotification() {
+ if (NowFarming) {
+ FarmResetEvent.Set();
+ return;
+ }
+
+ // If we're not farming, and we got new items, it's likely to be a booster pack or likewise
+ // In this case, perform a loot if user wants to do so
+ await Bot.LootIfNeeded().ConfigureAwait(false);
+ }
internal async Task Pause(bool sticky) {
if (sticky) {
@@ -142,6 +134,8 @@ namespace ArchiSteamFarm {
}
}
+ internal void SetInitialState(bool paused) => StickyPause = Paused = paused;
+
internal async Task StartFarming() {
if (NowFarming || Paused || !Bot.IsPlayingPossible) {
return;
@@ -257,117 +251,12 @@ namespace ArchiSteamFarm {
}
}
- internal void OnDisconnected() => StopFarming().Forget();
-
- internal async Task OnNewItemsNotification() {
- if (NowFarming) {
- FarmResetEvent.Set();
+ private void CheckGamesForFarming() {
+ if (NowFarming || Paused || !Bot.IsConnectedAndLoggedOn) {
return;
}
- // If we're not farming, and we got new items, it's likely to be a booster pack or likewise
- // In this case, perform a loot if user wants to do so
- await Bot.LootIfNeeded().ConfigureAwait(false);
- }
-
- internal async Task OnNewGameAdded() {
- if (!NowFarming) {
- // If we're not farming yet, obviously it's worth it to make a check
- StartFarming().Forget();
- return;
- }
-
- if (Bot.BotConfig.CardDropsRestricted && (GamesToFarm.Count > 0) && (GamesToFarm.Min(game => game.HoursPlayed) < 2)) {
- // If we have Complex algorithm and some games to boost, it's also worth to make a check
- // That's because we would check for new games after our current round anyway
- await StopFarming().ConfigureAwait(false);
- StartFarming().Forget();
- }
- }
-
- private async Task IsAnythingToFarm() {
- Bot.ArchiLogger.LogGenericInfo("Checking badges...");
-
- // Find the number of badge pages
- Bot.ArchiLogger.LogGenericInfo("Checking first page...");
- HtmlDocument htmlDocument = await Bot.ArchiWebHandler.GetBadgePage(1).ConfigureAwait(false);
- if (htmlDocument == null) {
- Bot.ArchiLogger.LogGenericWarning("Could not get badges information, will try again later!");
- return false;
- }
-
- byte maxPages = 1;
-
- HtmlNode htmlNode = htmlDocument.DocumentNode.SelectSingleNode("(//a[@class='pagelink'])[last()]");
- if (htmlNode != null) {
- string lastPage = htmlNode.InnerText;
- if (string.IsNullOrEmpty(lastPage)) {
- Bot.ArchiLogger.LogNullError(nameof(lastPage));
- return false;
- }
-
- if (!byte.TryParse(lastPage, out maxPages) || (maxPages == 0)) {
- Bot.ArchiLogger.LogNullError(nameof(maxPages));
- return false;
- }
- }
-
- GamesToFarm.ClearAndTrim();
- CheckPage(htmlDocument);
-
- if (maxPages == 1) {
- SortGamesToFarm();
- return GamesToFarm.Count > 0;
- }
-
- Bot.ArchiLogger.LogGenericInfo("Checking other pages...");
-
- List tasks = new List(maxPages - 1);
- for (byte page = 2; page <= maxPages; page++) {
- byte currentPage = page; // We need a copy of variable being passed when in for loops, as loop will proceed before task is launched
- tasks.Add(CheckPage(currentPage));
- }
-
- await Task.WhenAll(tasks).ConfigureAwait(false);
- SortGamesToFarm();
- return GamesToFarm.Count > 0;
- }
-
- private void SortGamesToFarm() {
- IOrderedEnumerable gamesToFarm;
- switch (Bot.BotConfig.FarmingOrder) {
- case BotConfig.EFarmingOrder.Unordered:
- return;
- case BotConfig.EFarmingOrder.AppIDsAscending:
- gamesToFarm = GamesToFarm.OrderBy(game => game.AppID);
- break;
- case BotConfig.EFarmingOrder.AppIDsDescending:
- gamesToFarm = GamesToFarm.OrderByDescending(game => game.AppID);
- break;
- case BotConfig.EFarmingOrder.CardDropsAscending:
- gamesToFarm = GamesToFarm.OrderBy(game => game.CardsRemaining);
- break;
- case BotConfig.EFarmingOrder.CardDropsDescending:
- gamesToFarm = GamesToFarm.OrderByDescending(game => game.CardsRemaining);
- break;
- case BotConfig.EFarmingOrder.HoursAscending:
- gamesToFarm = GamesToFarm.OrderBy(game => game.HoursPlayed);
- break;
- case BotConfig.EFarmingOrder.HoursDescending:
- gamesToFarm = GamesToFarm.OrderByDescending(game => game.HoursPlayed);
- break;
- case BotConfig.EFarmingOrder.NamesAscending:
- gamesToFarm = GamesToFarm.OrderBy(game => game.GameName);
- break;
- case BotConfig.EFarmingOrder.NamesDescending:
- gamesToFarm = GamesToFarm.OrderByDescending(game => game.GameName);
- break;
- default:
- Bot.ArchiLogger.LogGenericError("Unhandled case: " + Bot.BotConfig.FarmingOrder);
- return;
- }
-
- GamesToFarm.ReplaceWith(gamesToFarm.ToList()); // We must call ToList() here as we can't enumerate during replacing
+ StartFarming().Forget();
}
private void CheckPage(HtmlDocument htmlDocument) {
@@ -513,92 +402,6 @@ namespace ArchiSteamFarm {
CheckPage(htmlDocument);
}
- private void CheckGamesForFarming() {
- if (NowFarming || Paused || !Bot.IsConnectedAndLoggedOn) {
- return;
- }
-
- StartFarming().Forget();
- }
-
- private async Task ShouldFarm(Game game) {
- if (game == null) {
- Bot.ArchiLogger.LogNullError(nameof(game));
- return false;
- }
-
- HtmlDocument htmlDocument = await Bot.ArchiWebHandler.GetGameCardsPage(game.AppID).ConfigureAwait(false);
- if (htmlDocument == null) {
- return null;
- }
-
- HtmlNode htmlNode = htmlDocument.DocumentNode.SelectSingleNode("//span[@class='progress_info_bold']");
- if (htmlNode == null) {
- Bot.ArchiLogger.LogNullError(nameof(htmlNode));
- return null;
- }
-
- string progress = htmlNode.InnerText;
- if (string.IsNullOrEmpty(progress)) {
- Bot.ArchiLogger.LogNullError(nameof(progress));
- return null;
- }
-
- ushort cardsRemaining = 0;
-
- Match match = Regex.Match(progress, @"\d+");
- if (match.Success) {
- if (!ushort.TryParse(match.Value, out cardsRemaining)) {
- Bot.ArchiLogger.LogNullError(nameof(cardsRemaining));
- return null;
- }
- }
-
- game.CardsRemaining = cardsRemaining;
-
- Bot.ArchiLogger.LogGenericInfo("Status for " + game.AppID + " (" + game.GameName + "): " + cardsRemaining + " cards remaining");
- return cardsRemaining > 0;
- }
-
- private bool FarmMultiple(IEnumerable games) {
- if (games == null) {
- Bot.ArchiLogger.LogNullError(nameof(games));
- return false;
- }
-
- CurrentGamesFarming.ReplaceWith(games);
-
- Bot.ArchiLogger.LogGenericInfo("Now farming: " + string.Join(", ", CurrentGamesFarming.Select(game => game.AppID)));
-
- bool result = FarmHours(CurrentGamesFarming);
- CurrentGamesFarming.ClearAndTrim();
- return result;
- }
-
- private async Task FarmSolo(Game game) {
- if (game == null) {
- Bot.ArchiLogger.LogNullError(nameof(game));
- return true;
- }
-
- CurrentGamesFarming.Add(game);
-
- Bot.ArchiLogger.LogGenericInfo("Now farming: " + game.AppID + " (" + game.GameName + ")");
-
- bool result = await Farm(game).ConfigureAwait(false);
- CurrentGamesFarming.ClearAndTrim();
-
- if (!result) {
- return false;
- }
-
- GamesToFarm.Remove(game);
-
- TimeSpan timeSpan = TimeSpan.FromHours(game.HoursPlayed);
- Bot.ArchiLogger.LogGenericInfo("Done farming: " + game.AppID + " (" + game.GameName + ") after " + timeSpan.ToString(@"hh\:mm") + " hours of playtime!");
- return true;
- }
-
private async Task Farm(Game game) {
if (game == null) {
Bot.ArchiLogger.LogNullError(nameof(game));
@@ -615,7 +418,7 @@ namespace ArchiSteamFarm {
Bot.ArchiLogger.LogGenericInfo("Still farming: " + game.AppID + " (" + game.GameName + ")");
DateTime startFarmingPeriod = DateTime.Now;
- if (FarmResetEvent.Wait(60 * 1000 * Program.GlobalConfig.FarmingDelay)) {
+ if (FarmResetEvent.Wait(60*1000*Program.GlobalConfig.FarmingDelay)) {
FarmResetEvent.Reset();
success = KeepFarming;
}
@@ -658,7 +461,7 @@ namespace ArchiSteamFarm {
Bot.ArchiLogger.LogGenericInfo("Still farming: " + string.Join(", ", games.Select(game => game.AppID)));
DateTime startFarmingPeriod = DateTime.Now;
- if (FarmResetEvent.Wait(60 * 1000 * Program.GlobalConfig.FarmingDelay)) {
+ if (FarmResetEvent.Wait(60*1000*Program.GlobalConfig.FarmingDelay)) {
FarmResetEvent.Reset();
success = KeepFarming;
}
@@ -680,15 +483,211 @@ namespace ArchiSteamFarm {
return success;
}
- public void Dispose() {
- // Those are objects that are always being created if constructor doesn't throw exception
- CurrentGamesFarming.Dispose();
- GamesToFarm.Dispose();
- FarmingSemaphore.Dispose();
- FarmResetEvent.Dispose();
+ private bool FarmMultiple(IEnumerable games) {
+ if (games == null) {
+ Bot.ArchiLogger.LogNullError(nameof(games));
+ return false;
+ }
- // Those are objects that might be null and the check should be in-place
- IdleFarmingTimer?.Dispose();
+ CurrentGamesFarming.ReplaceWith(games);
+
+ Bot.ArchiLogger.LogGenericInfo("Now farming: " + string.Join(", ", CurrentGamesFarming.Select(game => game.AppID)));
+
+ bool result = FarmHours(CurrentGamesFarming);
+ CurrentGamesFarming.ClearAndTrim();
+ return result;
+ }
+
+ private async Task FarmSolo(Game game) {
+ if (game == null) {
+ Bot.ArchiLogger.LogNullError(nameof(game));
+ return true;
+ }
+
+ CurrentGamesFarming.Add(game);
+
+ Bot.ArchiLogger.LogGenericInfo("Now farming: " + game.AppID + " (" + game.GameName + ")");
+
+ bool result = await Farm(game).ConfigureAwait(false);
+ CurrentGamesFarming.ClearAndTrim();
+
+ if (!result) {
+ return false;
+ }
+
+ GamesToFarm.Remove(game);
+
+ TimeSpan timeSpan = TimeSpan.FromHours(game.HoursPlayed);
+ Bot.ArchiLogger.LogGenericInfo("Done farming: " + game.AppID + " (" + game.GameName + ") after " + timeSpan.ToString(@"hh\:mm") + " hours of playtime!");
+ return true;
+ }
+
+ private async Task IsAnythingToFarm() {
+ Bot.ArchiLogger.LogGenericInfo("Checking badges...");
+
+ // Find the number of badge pages
+ Bot.ArchiLogger.LogGenericInfo("Checking first page...");
+ HtmlDocument htmlDocument = await Bot.ArchiWebHandler.GetBadgePage(1).ConfigureAwait(false);
+ if (htmlDocument == null) {
+ Bot.ArchiLogger.LogGenericWarning("Could not get badges information, will try again later!");
+ return false;
+ }
+
+ byte maxPages = 1;
+
+ HtmlNode htmlNode = htmlDocument.DocumentNode.SelectSingleNode("(//a[@class='pagelink'])[last()]");
+ if (htmlNode != null) {
+ string lastPage = htmlNode.InnerText;
+ if (string.IsNullOrEmpty(lastPage)) {
+ Bot.ArchiLogger.LogNullError(nameof(lastPage));
+ return false;
+ }
+
+ if (!byte.TryParse(lastPage, out maxPages) || (maxPages == 0)) {
+ Bot.ArchiLogger.LogNullError(nameof(maxPages));
+ return false;
+ }
+ }
+
+ GamesToFarm.ClearAndTrim();
+ CheckPage(htmlDocument);
+
+ if (maxPages == 1) {
+ SortGamesToFarm();
+ return GamesToFarm.Count > 0;
+ }
+
+ Bot.ArchiLogger.LogGenericInfo("Checking other pages...");
+
+ List tasks = new List(maxPages - 1);
+ for (byte page = 2; page <= maxPages; page++) {
+ byte currentPage = page; // We need a copy of variable being passed when in for loops, as loop will proceed before task is launched
+ tasks.Add(CheckPage(currentPage));
+ }
+
+ await Task.WhenAll(tasks).ConfigureAwait(false);
+ SortGamesToFarm();
+ return GamesToFarm.Count > 0;
+ }
+
+ private async Task ShouldFarm(Game game) {
+ if (game == null) {
+ Bot.ArchiLogger.LogNullError(nameof(game));
+ return false;
+ }
+
+ HtmlDocument htmlDocument = await Bot.ArchiWebHandler.GetGameCardsPage(game.AppID).ConfigureAwait(false);
+ if (htmlDocument == null) {
+ return null;
+ }
+
+ HtmlNode htmlNode = htmlDocument.DocumentNode.SelectSingleNode("//span[@class='progress_info_bold']");
+ if (htmlNode == null) {
+ Bot.ArchiLogger.LogNullError(nameof(htmlNode));
+ return null;
+ }
+
+ string progress = htmlNode.InnerText;
+ if (string.IsNullOrEmpty(progress)) {
+ Bot.ArchiLogger.LogNullError(nameof(progress));
+ return null;
+ }
+
+ ushort cardsRemaining = 0;
+
+ Match match = Regex.Match(progress, @"\d+");
+ if (match.Success) {
+ if (!ushort.TryParse(match.Value, out cardsRemaining)) {
+ Bot.ArchiLogger.LogNullError(nameof(cardsRemaining));
+ return null;
+ }
+ }
+
+ game.CardsRemaining = cardsRemaining;
+
+ Bot.ArchiLogger.LogGenericInfo("Status for " + game.AppID + " (" + game.GameName + "): " + cardsRemaining + " cards remaining");
+ return cardsRemaining > 0;
+ }
+
+ private void SortGamesToFarm() {
+ IOrderedEnumerable gamesToFarm;
+ switch (Bot.BotConfig.FarmingOrder) {
+ case BotConfig.EFarmingOrder.Unordered:
+ return;
+ case BotConfig.EFarmingOrder.AppIDsAscending:
+ gamesToFarm = GamesToFarm.OrderBy(game => game.AppID);
+ break;
+ case BotConfig.EFarmingOrder.AppIDsDescending:
+ gamesToFarm = GamesToFarm.OrderByDescending(game => game.AppID);
+ break;
+ case BotConfig.EFarmingOrder.CardDropsAscending:
+ gamesToFarm = GamesToFarm.OrderBy(game => game.CardsRemaining);
+ break;
+ case BotConfig.EFarmingOrder.CardDropsDescending:
+ gamesToFarm = GamesToFarm.OrderByDescending(game => game.CardsRemaining);
+ break;
+ case BotConfig.EFarmingOrder.HoursAscending:
+ gamesToFarm = GamesToFarm.OrderBy(game => game.HoursPlayed);
+ break;
+ case BotConfig.EFarmingOrder.HoursDescending:
+ gamesToFarm = GamesToFarm.OrderByDescending(game => game.HoursPlayed);
+ break;
+ case BotConfig.EFarmingOrder.NamesAscending:
+ gamesToFarm = GamesToFarm.OrderBy(game => game.GameName);
+ break;
+ case BotConfig.EFarmingOrder.NamesDescending:
+ gamesToFarm = GamesToFarm.OrderByDescending(game => game.GameName);
+ break;
+ default:
+ Bot.ArchiLogger.LogGenericError("Unhandled case: " + Bot.BotConfig.FarmingOrder);
+ return;
+ }
+
+ GamesToFarm.ReplaceWith(gamesToFarm.ToList()); // We must call ToList() here as we can't enumerate during replacing
+ }
+
+ internal sealed class Game {
+ [JsonProperty]
+ internal readonly uint AppID;
+
+ [JsonProperty]
+ internal readonly string GameName;
+
+ [JsonProperty]
+ internal ushort CardsRemaining { get; set; }
+
+ [JsonProperty]
+ internal float HoursPlayed { get; set; }
+
+ //internal string HeaderURL => "https://steamcdn-a.akamaihd.net/steam/apps/" + AppID + "/header.jpg";
+
+ internal Game(uint appID, string gameName, float hoursPlayed, ushort cardsRemaining) {
+ if ((appID == 0) || string.IsNullOrEmpty(gameName) || (hoursPlayed < 0) || (cardsRemaining == 0)) {
+ throw new ArgumentOutOfRangeException(nameof(appID) + " || " + nameof(gameName) + " || " + nameof(hoursPlayed) + " || " + nameof(cardsRemaining));
+ }
+
+ AppID = appID;
+ GameName = gameName;
+ HoursPlayed = hoursPlayed;
+ CardsRemaining = cardsRemaining;
+ }
+
+ public override bool Equals(object obj) {
+ if (ReferenceEquals(null, obj)) {
+ return false;
+ }
+
+ if (ReferenceEquals(this, obj)) {
+ return true;
+ }
+
+ Game game = obj as Game;
+ return (game != null) && Equals(game);
+ }
+
+ public override int GetHashCode() => (int) AppID;
+
+ private bool Equals(Game other) => AppID == other.AppID;
}
}
-}
+}
\ No newline at end of file
diff --git a/ArchiSteamFarm/ConcurrentEnumerator.cs b/ArchiSteamFarm/ConcurrentEnumerator.cs
index a9e0268bc..cd27b4ef0 100644
--- a/ArchiSteamFarm/ConcurrentEnumerator.cs
+++ b/ArchiSteamFarm/ConcurrentEnumerator.cs
@@ -31,11 +31,11 @@ namespace ArchiSteamFarm {
internal sealed class ConcurrentEnumerator : IEnumerator {
public T Current => Enumerator.Current;
- object IEnumerator.Current => Current;
-
private readonly IEnumerator Enumerator;
private readonly ReaderWriterLockSlim Lock;
+ object IEnumerator.Current => Current;
+
internal ConcurrentEnumerator(ICollection collection, ReaderWriterLockSlim rwLock) {
if ((collection == null) || (rwLock == null)) {
throw new ArgumentNullException(nameof(collection) + " || " + nameof(rwLock));
@@ -47,9 +47,9 @@ namespace ArchiSteamFarm {
Enumerator = collection.GetEnumerator();
}
+ public void Dispose() => Lock?.ExitReadLock();
+
public bool MoveNext() => Enumerator.MoveNext();
public void Reset() => Enumerator.Reset();
-
- public void Dispose() => Lock?.ExitReadLock();
}
-}
+}
\ No newline at end of file
diff --git a/ArchiSteamFarm/ConcurrentHashSet.cs b/ArchiSteamFarm/ConcurrentHashSet.cs
index 1b327e76b..bb45334f6 100644
--- a/ArchiSteamFarm/ConcurrentHashSet.cs
+++ b/ArchiSteamFarm/ConcurrentHashSet.cs
@@ -29,15 +29,6 @@ using System.Threading;
namespace ArchiSteamFarm {
internal sealed class ConcurrentHashSet : ICollection, IDisposable {
- private readonly HashSet HashSet = new HashSet();
- private readonly ReaderWriterLockSlim Lock = new ReaderWriterLockSlim();
-
- public bool IsReadOnly => false;
- public IEnumerator GetEnumerator() => new ConcurrentEnumerator(HashSet, Lock);
-
- IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
- void ICollection.Add(T item) => Add(item);
-
public int Count {
get {
Lock.EnterReadLock();
@@ -50,6 +41,11 @@ namespace ArchiSteamFarm {
}
}
+ public bool IsReadOnly => false;
+
+ private readonly HashSet HashSet = new HashSet();
+ private readonly ReaderWriterLockSlim Lock = new ReaderWriterLockSlim();
+
public void Clear() {
Lock.EnterWriteLock();
@@ -70,18 +66,6 @@ namespace ArchiSteamFarm {
}
}
- public bool Remove(T item) {
- Lock.EnterWriteLock();
-
- try {
- return HashSet.Remove(item);
- } finally {
- Lock.ExitWriteLock();
- }
- }
-
- public void Dispose() => Lock.Dispose();
-
public void CopyTo(T[] array, int arrayIndex) {
Lock.EnterReadLock();
@@ -92,6 +76,23 @@ namespace ArchiSteamFarm {
}
}
+ public void Dispose() => Lock.Dispose();
+ public IEnumerator GetEnumerator() => new ConcurrentEnumerator(HashSet, Lock);
+
+ public bool Remove(T item) {
+ Lock.EnterWriteLock();
+
+ try {
+ return HashSet.Remove(item);
+ } finally {
+ Lock.ExitWriteLock();
+ }
+ }
+
+ void ICollection.Add(T item) => Add(item);
+
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+
internal void Add(T item) {
Lock.EnterWriteLock();
@@ -102,6 +103,17 @@ namespace ArchiSteamFarm {
}
}
+ internal void ClearAndTrim() {
+ Lock.EnterWriteLock();
+
+ try {
+ HashSet.Clear();
+ HashSet.TrimExcess();
+ } finally {
+ Lock.ExitWriteLock();
+ }
+ }
+
internal bool ReplaceIfNeededWith(HashSet items) {
Lock.EnterUpgradeableReadLock();
@@ -133,17 +145,6 @@ namespace ArchiSteamFarm {
}
}
- internal void ClearAndTrim() {
- Lock.EnterWriteLock();
-
- try {
- HashSet.Clear();
- HashSet.TrimExcess();
- } finally {
- Lock.ExitWriteLock();
- }
- }
-
internal void TrimExcess() {
Lock.EnterWriteLock();
@@ -154,4 +155,4 @@ namespace ArchiSteamFarm {
}
}
}
-}
+}
\ No newline at end of file
diff --git a/ArchiSteamFarm/CryptoHelper.cs b/ArchiSteamFarm/CryptoHelper.cs
index ec32fa60b..5a2ca1fb9 100644
--- a/ArchiSteamFarm/CryptoHelper.cs
+++ b/ArchiSteamFarm/CryptoHelper.cs
@@ -28,21 +28,24 @@ using System.Text;
namespace ArchiSteamFarm {
internal static class CryptoHelper {
- internal enum ECryptoMethod : byte {
- PlainText,
- AES,
- ProtectedDataForCurrentUser
- }
-
private static byte[] EncryptionKey = Encoding.UTF8.GetBytes("ArchiSteamFarm");
- internal static void SetEncryptionKey(string key) {
- if (string.IsNullOrEmpty(key)) {
- ASF.ArchiLogger.LogNullError(nameof(key));
- return;
+ internal static string Decrypt(ECryptoMethod cryptoMethod, string encrypted) {
+ if (string.IsNullOrEmpty(encrypted)) {
+ ASF.ArchiLogger.LogNullError(nameof(encrypted));
+ return null;
}
- EncryptionKey = Encoding.UTF8.GetBytes(key);
+ switch (cryptoMethod) {
+ case ECryptoMethod.PlainText:
+ return encrypted;
+ case ECryptoMethod.AES:
+ return DecryptAES(encrypted);
+ case ECryptoMethod.ProtectedDataForCurrentUser:
+ return DecryptProtectedDataForCurrentUser(encrypted);
+ default:
+ return null;
+ }
}
internal static string Encrypt(ECryptoMethod cryptoMethod, string decrypted) {
@@ -63,21 +66,50 @@ namespace ArchiSteamFarm {
}
}
- internal static string Decrypt(ECryptoMethod cryptoMethod, string encrypted) {
+ internal static void SetEncryptionKey(string key) {
+ if (string.IsNullOrEmpty(key)) {
+ ASF.ArchiLogger.LogNullError(nameof(key));
+ return;
+ }
+
+ EncryptionKey = Encoding.UTF8.GetBytes(key);
+ }
+
+ private static string DecryptAES(string encrypted) {
if (string.IsNullOrEmpty(encrypted)) {
ASF.ArchiLogger.LogNullError(nameof(encrypted));
return null;
}
- switch (cryptoMethod) {
- case ECryptoMethod.PlainText:
- return encrypted;
- case ECryptoMethod.AES:
- return DecryptAES(encrypted);
- case ECryptoMethod.ProtectedDataForCurrentUser:
- return DecryptProtectedDataForCurrentUser(encrypted);
- default:
- return null;
+ try {
+ byte[] key;
+ using (SHA256Cng sha256 = new SHA256Cng()) {
+ key = sha256.ComputeHash(EncryptionKey);
+ }
+
+ byte[] decryptedData = Convert.FromBase64String(encrypted);
+ decryptedData = SteamKit2.CryptoHelper.SymmetricDecrypt(decryptedData, key);
+ return Encoding.UTF8.GetString(decryptedData);
+ } catch (Exception e) {
+ ASF.ArchiLogger.LogGenericException(e);
+ return null;
+ }
+ }
+
+ private static string DecryptProtectedDataForCurrentUser(string encrypted) {
+ if (string.IsNullOrEmpty(encrypted)) {
+ ASF.ArchiLogger.LogNullError(nameof(encrypted));
+ return null;
+ }
+
+ try {
+ byte[] decryptedData = ProtectedData.Unprotect(Convert.FromBase64String(encrypted), EncryptionKey, // This is used as salt only and it's fine that it's known
+ DataProtectionScope.CurrentUser);
+
+ return Encoding.UTF8.GetString(decryptedData);
+ } catch (Exception e) {
+ ASF.ArchiLogger.LogGenericException(e);
+ return null;
}
}
@@ -102,27 +134,6 @@ namespace ArchiSteamFarm {
}
}
- private static string DecryptAES(string encrypted) {
- if (string.IsNullOrEmpty(encrypted)) {
- ASF.ArchiLogger.LogNullError(nameof(encrypted));
- return null;
- }
-
- try {
- byte[] key;
- using (SHA256Cng sha256 = new SHA256Cng()) {
- key = sha256.ComputeHash(EncryptionKey);
- }
-
- byte[] decryptedData = Convert.FromBase64String(encrypted);
- decryptedData = SteamKit2.CryptoHelper.SymmetricDecrypt(decryptedData, key);
- return Encoding.UTF8.GetString(decryptedData);
- } catch (Exception e) {
- ASF.ArchiLogger.LogGenericException(e);
- return null;
- }
- }
-
private static string EncryptProtectedDataForCurrentUser(string decrypted) {
if (string.IsNullOrEmpty(decrypted)) {
ASF.ArchiLogger.LogNullError(nameof(decrypted));
@@ -130,11 +141,8 @@ namespace ArchiSteamFarm {
}
try {
- byte[] encryptedData = ProtectedData.Protect(
- Encoding.UTF8.GetBytes(decrypted),
- EncryptionKey, // This is used as salt only and it's fine that it's known
- DataProtectionScope.CurrentUser
- );
+ byte[] encryptedData = ProtectedData.Protect(Encoding.UTF8.GetBytes(decrypted), EncryptionKey, // This is used as salt only and it's fine that it's known
+ DataProtectionScope.CurrentUser);
return Convert.ToBase64String(encryptedData);
} catch (Exception e) {
@@ -143,24 +151,10 @@ namespace ArchiSteamFarm {
}
}
- private static string DecryptProtectedDataForCurrentUser(string encrypted) {
- if (string.IsNullOrEmpty(encrypted)) {
- ASF.ArchiLogger.LogNullError(nameof(encrypted));
- return null;
- }
-
- try {
- byte[] decryptedData = ProtectedData.Unprotect(
- Convert.FromBase64String(encrypted),
- EncryptionKey, // This is used as salt only and it's fine that it's known
- DataProtectionScope.CurrentUser
- );
-
- return Encoding.UTF8.GetString(decryptedData);
- } catch (Exception e) {
- ASF.ArchiLogger.LogGenericException(e);
- return null;
- }
+ internal enum ECryptoMethod : byte {
+ PlainText,
+ AES,
+ ProtectedDataForCurrentUser
}
}
-}
+}
\ No newline at end of file
diff --git a/ArchiSteamFarm/Debugging.cs b/ArchiSteamFarm/Debugging.cs
index af79f1d86..47b06f91d 100644
--- a/ArchiSteamFarm/Debugging.cs
+++ b/ArchiSteamFarm/Debugging.cs
@@ -22,8 +22,8 @@
*/
-using SteamKit2;
using System.Diagnostics.CodeAnalysis;
+using SteamKit2;
namespace ArchiSteamFarm {
internal static class Debugging {
@@ -46,4 +46,4 @@ namespace ArchiSteamFarm {
}
}
}
-}
+}
\ No newline at end of file
diff --git a/ArchiSteamFarm/Events.cs b/ArchiSteamFarm/Events.cs
index 0c4ce5359..085220e26 100644
--- a/ArchiSteamFarm/Events.cs
+++ b/ArchiSteamFarm/Events.cs
@@ -28,9 +28,6 @@ using SteamKit2;
namespace ArchiSteamFarm {
internal static class Events {
- internal static void OnStateUpdated(Bot bot, SteamFriends.PersonaStateCallback callback) {
- }
-
internal static async void OnBotShutdown() {
if (Program.IsWCFRunning || Bot.Bots.Values.Any(bot => bot.KeepRunning)) {
return;
@@ -40,5 +37,7 @@ namespace ArchiSteamFarm {
await Task.Delay(5000).ConfigureAwait(false);
Program.Shutdown();
}
+
+ internal static void OnStateUpdated(Bot bot, SteamFriends.PersonaStateCallback callback) { }
}
-}
+}
\ No newline at end of file
diff --git a/ArchiSteamFarm/GlobalConfig.cs b/ArchiSteamFarm/GlobalConfig.cs
index 3c2ae80e1..b19d4c6bb 100644
--- a/ArchiSteamFarm/GlobalConfig.cs
+++ b/ArchiSteamFarm/GlobalConfig.cs
@@ -22,94 +22,90 @@
*/
-using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Net.Sockets;
+using Newtonsoft.Json;
namespace ArchiSteamFarm {
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
[SuppressMessage("ReSharper", "ConvertToConstant.Global")]
internal sealed class GlobalConfig {
- [SuppressMessage("ReSharper", "UnusedMember.Global")]
- internal enum EUpdateChannel : byte {
- None,
- Stable,
- Experimental
- }
-
internal const byte DefaultHttpTimeout = 60;
- private const byte DefaultMaxFarmingTime = 10;
private const byte DefaultFarmingDelay = 15;
- private const ushort DefaultWCFPort = 1242;
+ private const byte DefaultMaxFarmingTime = 10;
private const ProtocolType DefaultSteamProtocol = ProtocolType.Tcp;
+ private const ushort DefaultWCFPort = 1242;
// This is hardcoded blacklist which should not be possible to change
internal static readonly HashSet GlobalBlacklist = new HashSet { 267420, 303700, 335590, 368020, 425280, 480730 };
[JsonProperty(Required = Required.DisallowNull)]
- internal readonly bool Debug = false;
-
- [JsonProperty(Required = Required.DisallowNull)]
- internal readonly bool Headless = false;
+ internal readonly bool AutoRestart = true;
[JsonProperty(Required = Required.DisallowNull)]
internal readonly bool AutoUpdates = true;
[JsonProperty(Required = Required.DisallowNull)]
- internal readonly bool AutoRestart = true;
+ internal readonly HashSet Blacklist = new HashSet(GlobalBlacklist);
[JsonProperty(Required = Required.DisallowNull)]
- internal readonly EUpdateChannel UpdateChannel = EUpdateChannel.Stable;
-
- [JsonProperty(Required = Required.DisallowNull)]
- internal readonly ProtocolType SteamProtocol = DefaultSteamProtocol;
-
- [JsonProperty(Required = Required.DisallowNull)]
- internal readonly ulong SteamOwnerID = 0;
-
- [JsonProperty(Required = Required.DisallowNull)]
- internal readonly byte MaxFarmingTime = DefaultMaxFarmingTime;
-
- [JsonProperty(Required = Required.DisallowNull)]
- internal readonly byte IdleFarmingPeriod = 3;
+ internal readonly bool Debug = false;
[JsonProperty(Required = Required.DisallowNull)]
internal readonly byte FarmingDelay = DefaultFarmingDelay;
[JsonProperty(Required = Required.DisallowNull)]
- internal readonly byte LoginLimiterDelay = 10;
-
- [JsonProperty(Required = Required.DisallowNull)]
- internal readonly byte InventoryLimiterDelay = 3;
+ internal readonly bool ForceHttp = false;
[JsonProperty(Required = Required.DisallowNull)]
internal readonly byte GiftsLimiterDelay = 1;
[JsonProperty(Required = Required.DisallowNull)]
- internal readonly byte MaxTradeHoldDuration = 15;
-
- [JsonProperty(Required = Required.DisallowNull)]
- internal readonly bool ForceHttp = false;
+ internal readonly bool Headless = false;
[JsonProperty(Required = Required.DisallowNull)]
internal readonly byte HttpTimeout = DefaultHttpTimeout;
- [JsonProperty]
- internal string WCFHostname { get; set; } = "localhost";
+ [JsonProperty(Required = Required.DisallowNull)]
+ internal readonly byte IdleFarmingPeriod = 3;
[JsonProperty(Required = Required.DisallowNull)]
- internal readonly ushort WCFPort = DefaultWCFPort;
+ internal readonly byte InventoryLimiterDelay = 3;
+
+ [JsonProperty(Required = Required.DisallowNull)]
+ internal readonly byte LoginLimiterDelay = 10;
+
+ [JsonProperty(Required = Required.DisallowNull)]
+ internal readonly byte MaxFarmingTime = DefaultMaxFarmingTime;
+
+ [JsonProperty(Required = Required.DisallowNull)]
+ internal readonly byte MaxTradeHoldDuration = 15;
[JsonProperty(Required = Required.DisallowNull)]
internal readonly bool Statistics = true;
[JsonProperty(Required = Required.DisallowNull)]
- internal readonly HashSet Blacklist = new HashSet(GlobalBlacklist);
+ internal readonly ulong SteamOwnerID = 0;
+
+ [JsonProperty(Required = Required.DisallowNull)]
+ internal readonly ProtocolType SteamProtocol = DefaultSteamProtocol;
+
+ [JsonProperty(Required = Required.DisallowNull)]
+ internal readonly EUpdateChannel UpdateChannel = EUpdateChannel.Stable;
+
+ [JsonProperty(Required = Required.DisallowNull)]
+ internal readonly ushort WCFPort = DefaultWCFPort;
+
+ [JsonProperty]
+ internal string WCFHostname { get; set; } = "localhost";
+
+ // This constructor is used only by deserializer
+ private GlobalConfig() { }
internal static GlobalConfig Load(string filePath) {
if (string.IsNullOrEmpty(filePath)) {
@@ -171,7 +167,11 @@ namespace ArchiSteamFarm {
return null;
}
- // This constructor is used only by deserializer
- private GlobalConfig() { }
+ [SuppressMessage("ReSharper", "UnusedMember.Global")]
+ internal enum EUpdateChannel : byte {
+ None,
+ Stable,
+ Experimental
+ }
}
-}
+}
\ No newline at end of file
diff --git a/ArchiSteamFarm/GlobalDatabase.cs b/ArchiSteamFarm/GlobalDatabase.cs
index 77d2b4790..80fe654fd 100644
--- a/ArchiSteamFarm/GlobalDatabase.cs
+++ b/ArchiSteamFarm/GlobalDatabase.cs
@@ -22,28 +22,23 @@
*/
-using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
+using Newtonsoft.Json;
namespace ArchiSteamFarm {
internal sealed class GlobalDatabase : IDisposable {
- private static readonly JsonSerializerSettings CustomSerializerSettings = new JsonSerializerSettings {
- Converters = new List(2) {
- new IPAddressConverter(),
- new IPEndPointConverter()
- }
- };
+ private static readonly JsonSerializerSettings CustomSerializerSettings = new JsonSerializerSettings { Converters = new List