Compare commits

...

48 Commits

Author SHA1 Message Date
JustArchi
07049e71c0 Misc 2016-03-12 06:01:55 +01:00
JustArchi
4710d9c1eb Add support for WinAuth, #144 2016-03-12 05:58:51 +01:00
JustArchi
d08462745a I swear no more bumps tonight 2016-03-11 21:42:16 +01:00
JustArchi
eb4e9ee077 Fix remaining crash 2016-03-11 21:40:25 +01:00
JustArchi
601a486b13 Bump 2016-03-11 19:58:03 +01:00
JustArchi
14867f470d Derp 2016-03-11 19:49:52 +01:00
JustArchi
187f0800b2 Add !update 2016-03-11 19:48:14 +01:00
JustArchi
3afa202d0b Misc 2016-03-11 19:42:15 +01:00
JustArchi
d33e76c8b0 Add !farm, closes #151 2016-03-11 19:39:25 +01:00
JustArchi
3061c55eaf Bump 2016-03-11 19:15:56 +01:00
JustArchi
621a1dc2cb Do not crash on bad configs 2016-03-11 18:49:29 +01:00
JustArchi
1a832780a2 Bump 2016-03-11 18:35:42 +01:00
JustArchi
ab531c80df Misc 2016-03-11 02:15:25 +01:00
JustArchi
f20ea0a87f Add FarmingPeriod 2016-03-11 02:07:20 +01:00
JustArchi
3e7f726afb Bump 2016-03-11 00:52:15 +01:00
JustArchi
d247515a03 Confirmations should be minutes-based 2016-03-10 22:54:59 +01:00
JustArchi
f084a3f219 Mark inventory on login if needed 2016-03-10 21:44:45 +01:00
JustArchi
4d3673c305 Forgot to add 2016-03-10 21:19:21 +01:00
JustArchi
0253c3bf7b Add AcceptConfirmationsPeriod 2016-03-10 21:17:48 +01:00
JustArchi
0eae895676 SteamAuth update 2016-03-10 21:11:54 +01:00
JustArchi
045acb362d Code review 2016-03-10 02:21:28 +01:00
JustArchi
ebd65da3ff Misc 2016-03-10 01:23:32 +01:00
JustArchi
1de3c5bec0 Code review 2016-03-10 01:20:17 +01:00
JustArchi
b334c939df Add !2faok 2016-03-10 00:40:30 +01:00
JustArchi
8e6100e236 Add MaxFarmingTime, FarmingDelay 2016-03-09 19:25:45 +01:00
JustArchi
8cb512b6e5 Add InventoryLimiterDelay, rename RequestLimiterDelay to LoginLimiterDelay 2016-03-09 18:58:14 +01:00
JustArchi
6a28946205 Refresh farming queue when farming is finished 2016-03-09 18:50:54 +01:00
JustArchi
c632c025cb Move Statistics to global config 2016-03-09 05:02:07 +01:00
JustArchi
1403ffe190 This actually DOES work on Mono, hooray! 2016-03-09 03:52:43 +01:00
JustArchi
71ae9a84da Fix misc bug, #48 2016-03-09 03:52:04 +01:00
JustArchi
a823471771 Bump version to test updates 2016-03-09 03:40:57 +01:00
JustArchi
dac057d242 World Domination is close 2016-03-09 03:28:05 +01:00
JustArchi
3d9fe36245 The end is near... #48 2016-03-09 03:10:33 +01:00
JustArchi
5c1da24def Misc 2016-03-08 03:29:48 +01:00
JustArchi
1e961a5945 Add CustomGamePlayedWhileIdle 2016-03-08 03:18:50 +01:00
JustArchi
84898146d2 Add more debugging tools 2016-03-08 02:22:00 +01:00
JustArchi
552613e977 Add AccountPlayingDelay, closes #122 2016-03-07 23:10:07 +01:00
JustArchi
07687df91f Add debugging and NetHookAnalyzer 2016-03-07 18:12:05 +01:00
JustArchi
1a4d941a2c Fix derp, #141 2016-03-07 16:40:34 +01:00
JustArchi
cfbd880995 Add more support for SDA 2016-03-07 15:53:46 +01:00
JustArchi
09abe77495 Support SDA files 2016-03-07 15:42:38 +01:00
JustArchi
ac9943ff94 Misc 2016-03-07 14:48:59 +01:00
JustArchi
741dd2adb7 Add Holiday Sale 2013 to blacklist 2016-03-07 14:47:28 +01:00
JustArchi
1ad5d3676f Add GlobalDatabase 2016-03-07 02:39:55 +01:00
JustArchi
27254aa31e Copy ASF.json to out 2016-03-06 23:34:46 +01:00
JustArchi
bb90dc1c01 Bugfixes 2016-03-06 23:32:17 +01:00
JustArchi
292ec97b1c Work on GlobalConfig, #131 2016-03-06 23:28:56 +01:00
JustArchi
238cc2ad46 Code review 2016-03-06 22:14:02 +01:00
32 changed files with 993 additions and 353 deletions

4
.gitignore vendored
View File

@@ -4,11 +4,13 @@
# Ignore all config files, apart from ones we want to include # Ignore all config files, apart from ones we want to include
ArchiSteamFarm/config/* ArchiSteamFarm/config/*
!ArchiSteamFarm/config/ASF.json
!ArchiSteamFarm/config/example.json !ArchiSteamFarm/config/example.json
!ArchiSteamFarm/config/minimal.json !ArchiSteamFarm/config/minimal.json
# Ignore local debugging log file # Ignore local debugging
ArchiSteamFarm/log.txt ArchiSteamFarm/log.txt
ArchiSteamFarm/debug/*
################# #################
## Eclipse ## Eclipse

View File

@@ -50,7 +50,11 @@ namespace ArchiSteamFarm {
Items = 514 Items = 514
} }
internal ENotificationType NotificationType { get; set; } internal readonly ENotificationType NotificationType;
internal Notification(ENotificationType notificationType) {
NotificationType = notificationType;
}
} }
internal readonly List<Notification> Notifications; internal readonly List<Notification> Notifications;
@@ -64,9 +68,7 @@ namespace ArchiSteamFarm {
Notifications = new List<Notification>(msg.notifications.Count); Notifications = new List<Notification>(msg.notifications.Count);
foreach (var notification in msg.notifications) { foreach (var notification in msg.notifications) {
Notifications.Add(new Notification { Notifications.Add(new Notification((Notification.ENotificationType) notification.user_notification_type));
NotificationType = (Notification.ENotificationType) notification.user_notification_type
});
} }
} }
@@ -79,7 +81,7 @@ namespace ArchiSteamFarm {
if (msg.count_new_items > 0) { if (msg.count_new_items > 0) {
Notifications = new List<Notification>(1) { Notifications = new List<Notification>(1) {
new Notification { NotificationType = Notification.ENotificationType.Items } new Notification(Notification.ENotificationType.Items)
}; };
} }
} }
@@ -102,7 +104,7 @@ namespace ArchiSteamFarm {
} }
internal sealed class PurchaseResponseCallback : CallbackMsg { internal sealed class PurchaseResponseCallback : CallbackMsg {
internal enum EPurchaseResult { internal enum EPurchaseResult : int {
Unknown = -1, Unknown = -1,
OK = 0, OK = 0,
AlreadyOwned = 9, AlreadyOwned = 9,
@@ -128,6 +130,10 @@ namespace ArchiSteamFarm {
Result = (EResult) msg.eresult; Result = (EResult) msg.eresult;
PurchaseResult = (EPurchaseResult) msg.purchase_result_details; PurchaseResult = (EPurchaseResult) msg.purchase_result_details;
if (msg.purchase_receipt_info == null) {
return;
}
ReceiptInfo = new KeyValue(); ReceiptInfo = new KeyValue();
using (MemoryStream ms = new MemoryStream(msg.purchase_receipt_info)) { using (MemoryStream ms = new MemoryStream(msg.purchase_receipt_info)) {
if (!ReceiptInfo.TryReadAsBinary(ms)) { if (!ReceiptInfo.TryReadAsBinary(ms)) {
@@ -180,6 +186,27 @@ namespace ArchiSteamFarm {
Client.Send(request); Client.Send(request);
} }
internal void PlayGame(string gameName) {
if (!Client.IsConnected) {
return;
}
var request = new ClientMsgProtobuf<CMsgClientGamesPlayed>(EMsg.ClientGamesPlayed);
var gamePlayed = new CMsgClientGamesPlayed.GamePlayed();
if (!string.IsNullOrEmpty(gameName)) {
gamePlayed.game_id = new GameID() {
AppType = GameID.GameType.Shortcut,
ModID = uint.MaxValue
};
gamePlayed.game_extra_info = gameName;
}
request.Body.games_played.Add(gamePlayed);
Client.Send(request);
}
internal void PlayGames(params uint[] gameIDs) { internal void PlayGames(params uint[] gameIDs) {
if (!Client.IsConnected) { if (!Client.IsConnected) {
return; return;
@@ -200,7 +227,7 @@ namespace ArchiSteamFarm {
} }
internal void PlayGames(ICollection<uint> gameIDs) { internal void PlayGames(ICollection<uint> gameIDs) {
if (gameIDs == null || gameIDs.Count == 0 || !Client.IsConnected) { if (gameIDs == null || !Client.IsConnected) {
return; return;
} }

View File

@@ -12,6 +12,7 @@
<TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion> <TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment> <FileAlignment>512</FileAlignment>
<IsWebBootstrapper>false</IsWebBootstrapper> <IsWebBootstrapper>false</IsWebBootstrapper>
<TargetFrameworkProfile />
<PublishUrl>publish\</PublishUrl> <PublishUrl>publish\</PublishUrl>
<Install>true</Install> <Install>true</Install>
<InstallFrom>Disk</InstallFrom> <InstallFrom>Disk</InstallFrom>
@@ -26,7 +27,6 @@
<ApplicationVersion>1.0.0.%2a</ApplicationVersion> <ApplicationVersion>1.0.0.%2a</ApplicationVersion>
<UseApplicationTrust>false</UseApplicationTrust> <UseApplicationTrust>false</UseApplicationTrust>
<BootstrapperEnabled>true</BootstrapperEnabled> <BootstrapperEnabled>true</BootstrapperEnabled>
<TargetFrameworkProfile />
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget> <PlatformTarget>AnyCPU</PlatformTarget>
@@ -102,17 +102,17 @@
<Compile Include="ArchiWebHandler.cs" /> <Compile Include="ArchiWebHandler.cs" />
<Compile Include="Bot.cs" /> <Compile Include="Bot.cs" />
<Compile Include="BotConfig.cs" /> <Compile Include="BotConfig.cs" />
<Compile Include="GlobalDatabase.cs" />
<Compile Include="BotDatabase.cs" /> <Compile Include="BotDatabase.cs" />
<Compile Include="CardsFarmer.cs" /> <Compile Include="CardsFarmer.cs" />
<Compile Include="CMsgClientClanInviteAction.cs" /> <Compile Include="CMsgs\CMsgClientClanInviteAction.cs" />
<Compile Include="Debugging.cs" /> <Compile Include="Debugging.cs" />
<Compile Include="GlobalConfig.cs" />
<Compile Include="JSON\GitHub.cs" />
<Compile Include="JSON\Steam.cs" />
<Compile Include="Logging.cs" /> <Compile Include="Logging.cs" />
<Compile Include="Program.cs" /> <Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SteamItem.cs" />
<Compile Include="SteamItemList.cs" />
<Compile Include="SteamTradeOffer.cs" />
<Compile Include="SteamTradeOfferRequest.cs" />
<Compile Include="Trading.cs" /> <Compile Include="Trading.cs" />
<Compile Include="Utilities.cs" /> <Compile Include="Utilities.cs" />
<Compile Include="WCF.cs" /> <Compile Include="WCF.cs" />
@@ -120,6 +120,9 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="App.config" /> <None Include="App.config" />
<None Include="config\ASF.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="config\example.json"> <None Include="config\example.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>
@@ -158,6 +161,7 @@
<PropertyGroup> <PropertyGroup>
<PostBuildEvent Condition=" '$(OS)' != 'Unix' AND '$(ConfigurationName)' == 'Release' "> <PostBuildEvent Condition=" '$(OS)' != 'Unix' AND '$(ConfigurationName)' == 'Release' ">
mkdir "$(TargetDir)out" "$(TargetDir)out\config" mkdir "$(TargetDir)out" "$(TargetDir)out\config"
copy "$(TargetDir)config\ASF.json" "$(TargetDir)out\config"
copy "$(TargetDir)config\example.json" "$(TargetDir)out\config" copy "$(TargetDir)config\example.json" "$(TargetDir)out\config"
copy "$(TargetDir)config\minimal.json" "$(TargetDir)out\config" copy "$(TargetDir)config\minimal.json" "$(TargetDir)out\config"
"$(SolutionDir)tools\ILRepack.exe" /ndebug /internalize /parallel /targetplatform:v4 /wildcards /out:"$(TargetDir)out\ASF.exe" "$(TargetDir)$(TargetName).exe" "$(TargetDir)*.dll" "$(SolutionDir)tools\ILRepack.exe" /ndebug /internalize /parallel /targetplatform:v4 /wildcards /out:"$(TargetDir)out\ASF.exe" "$(TargetDir)$(TargetName).exe" "$(TargetDir)*.dll"
@@ -165,6 +169,7 @@
</PostBuildEvent> </PostBuildEvent>
<PostBuildEvent Condition=" '$(OS)' == 'Unix' AND '$(ConfigurationName)' == 'Release' "> <PostBuildEvent Condition=" '$(OS)' == 'Unix' AND '$(ConfigurationName)' == 'Release' ">
mkdir -p "$(TargetDir)out" "$(TargetDir)out/config" mkdir -p "$(TargetDir)out" "$(TargetDir)out/config"
cp "$(TargetDir)config/ASF.json" "$(TargetDir)out/config"
cp "$(TargetDir)config/example.json" "$(TargetDir)out/config" cp "$(TargetDir)config/example.json" "$(TargetDir)out/config"
cp "$(TargetDir)config/minimal.json" "$(TargetDir)out/config" cp "$(TargetDir)config/minimal.json" "$(TargetDir)out/config"
mono -O=all "$(SolutionDir)tools/ILRepack.exe" /ndebug /internalize /parallel /targetplatform:v4 /wildcards /out:"$(TargetDir)out/ASF.exe" "$(TargetDir)$(TargetName).exe" "$(TargetDir)*.dll" mono -O=all "$(SolutionDir)tools/ILRepack.exe" /ndebug /internalize /parallel /targetplatform:v4 /wildcards /out:"$(TargetDir)out/ASF.exe" "$(TargetDir)$(TargetName).exe" "$(TargetDir)*.dll"

View File

@@ -34,14 +34,22 @@ using System.Threading.Tasks;
namespace ArchiSteamFarm { namespace ArchiSteamFarm {
internal sealed class ArchiWebHandler { internal sealed class ArchiWebHandler {
private const int Timeout = 1000 * WebBrowser.HttpTimeout; // In miliseconds private static int Timeout = 30 * 1000;
private readonly Bot Bot; private readonly Bot Bot;
private readonly Dictionary<string, string> Cookie = new Dictionary<string, string>(4); private readonly Dictionary<string, string> Cookie = new Dictionary<string, string>(4);
private ulong SteamID; private ulong SteamID;
internal static void Init() {
Timeout = Program.GlobalConfig.HttpTimeout * 1000;
}
internal ArchiWebHandler(Bot bot) { internal ArchiWebHandler(Bot bot) {
if (bot == null) {
return;
}
Bot = bot; Bot = bot;
} }
@@ -58,7 +66,7 @@ namespace ArchiSteamFarm {
byte[] sessionKey = CryptoHelper.GenerateRandomBlock(32); byte[] sessionKey = CryptoHelper.GenerateRandomBlock(32);
// RSA encrypt it with the public key for the universe we're on // RSA encrypt it with the public key for the universe we're on
byte[] cryptedSessionKey = null; byte[] cryptedSessionKey;
using (RSACrypto rsa = new RSACrypto(KeyDictionary.GetPublicKey(steamClient.ConnectedUniverse))) { using (RSACrypto rsa = new RSACrypto(KeyDictionary.GetPublicKey(steamClient.ConnectedUniverse))) {
cryptedSessionKey = rsa.Encrypt(sessionKey); cryptedSessionKey = rsa.Encrypt(sessionKey);
} }
@@ -134,14 +142,14 @@ namespace ArchiSteamFarm {
bool? isLoggedIn = await IsLoggedIn().ConfigureAwait(false); bool? isLoggedIn = await IsLoggedIn().ConfigureAwait(false);
if (isLoggedIn.HasValue && !isLoggedIn.Value) { if (isLoggedIn.HasValue && !isLoggedIn.Value) {
Logging.LogGenericInfo("Reconnecting because our sessionID expired!", Bot.BotName); Logging.LogGenericInfo("Reconnecting because our sessionID expired!", Bot.BotName);
var restart = Task.Run(async () => await Bot.Restart().ConfigureAwait(false)); Task.Run(async () => await Bot.Restart().ConfigureAwait(false)).Forget();
return true; return true;
} }
return false; return false;
} }
internal List<SteamTradeOffer> GetTradeOffers() { internal List<Steam.TradeOffer> GetTradeOffers() {
if (string.IsNullOrEmpty(Bot.BotConfig.SteamApiKey)) { if (string.IsNullOrEmpty(Bot.BotConfig.SteamApiKey)) {
return null; return null;
} }
@@ -168,15 +176,15 @@ namespace ArchiSteamFarm {
return null; return null;
} }
List<SteamTradeOffer> result = new List<SteamTradeOffer>(); List<Steam.TradeOffer> result = new List<Steam.TradeOffer>();
foreach (KeyValue trade in response["trade_offers_received"].Children) { foreach (KeyValue trade in response["trade_offers_received"].Children) {
SteamTradeOffer tradeOffer = new SteamTradeOffer { Steam.TradeOffer tradeOffer = new Steam.TradeOffer {
tradeofferid = trade["tradeofferid"].AsString(), tradeofferid = trade["tradeofferid"].AsString(),
accountid_other = trade["accountid_other"].AsInteger(), accountid_other = trade["accountid_other"].AsInteger(),
trade_offer_state = trade["trade_offer_state"].AsEnum<SteamTradeOffer.ETradeOfferState>() trade_offer_state = trade["trade_offer_state"].AsEnum<Steam.TradeOffer.ETradeOfferState>()
}; };
foreach (KeyValue item in trade["items_to_give"].Children) { foreach (KeyValue item in trade["items_to_give"].Children) {
tradeOffer.items_to_give.Add(new SteamItem { tradeOffer.items_to_give.Add(new Steam.Item {
appid = item["appid"].AsString(), appid = item["appid"].AsString(),
contextid = item["contextid"].AsString(), contextid = item["contextid"].AsString(),
assetid = item["assetid"].AsString(), assetid = item["assetid"].AsString(),
@@ -186,7 +194,7 @@ namespace ArchiSteamFarm {
}); });
} }
foreach (KeyValue item in trade["items_to_receive"].Children) { foreach (KeyValue item in trade["items_to_receive"].Children) {
tradeOffer.items_to_receive.Add(new SteamItem { tradeOffer.items_to_receive.Add(new Steam.Item {
appid = item["appid"].AsString(), appid = item["appid"].AsString(),
contextid = item["contextid"].AsString(), contextid = item["contextid"].AsString(),
assetid = item["assetid"].AsString(), assetid = item["assetid"].AsString(),
@@ -293,7 +301,7 @@ namespace ArchiSteamFarm {
return true; return true;
} }
internal async Task<List<SteamItem>> GetMyTradableInventory() { internal async Task<List<Steam.Item>> GetMyTradableInventory() {
JObject jObject = null; JObject jObject = null;
for (byte i = 0; i < WebBrowser.MaxRetries && jObject == null; i++) { for (byte i = 0; i < WebBrowser.MaxRetries && jObject == null; i++) {
jObject = await WebBrowser.UrlGetToJObject("https://steamcommunity.com/my/inventory/json/753/6?trading=1", Cookie).ConfigureAwait(false); jObject = await WebBrowser.UrlGetToJObject("https://steamcommunity.com/my/inventory/json/753/6?trading=1", Cookie).ConfigureAwait(false);
@@ -310,10 +318,10 @@ namespace ArchiSteamFarm {
return null; return null;
} }
List<SteamItem> result = new List<SteamItem>(); List<Steam.Item> result = new List<Steam.Item>();
foreach (JToken jToken in jTokens) { foreach (JToken jToken in jTokens) {
try { try {
result.Add(JsonConvert.DeserializeObject<SteamItem>(jToken.ToString())); result.Add(JsonConvert.DeserializeObject<Steam.Item>(jToken.ToString()));
} catch (Exception e) { } catch (Exception e) {
Logging.LogGenericException(e, Bot.BotName); Logging.LogGenericException(e, Bot.BotName);
} }
@@ -322,7 +330,7 @@ namespace ArchiSteamFarm {
return result; return result;
} }
internal async Task<bool> SendTradeOffer(List<SteamItem> inventory, ulong partnerID, string token = null) { internal async Task<bool> SendTradeOffer(List<Steam.Item> inventory, ulong partnerID, string token = null) {
if (inventory == null || inventory.Count == 0 || partnerID == 0) { if (inventory == null || inventory.Count == 0 || partnerID == 0) {
return false; return false;
} }
@@ -332,21 +340,21 @@ namespace ArchiSteamFarm {
return false; return false;
} }
List<SteamTradeOfferRequest> trades = new List<SteamTradeOfferRequest>(1 + inventory.Count / Trading.MaxItemsPerTrade); List<Steam.TradeOfferRequest> trades = new List<Steam.TradeOfferRequest>(1 + inventory.Count / Trading.MaxItemsPerTrade);
SteamTradeOfferRequest singleTrade = null; Steam.TradeOfferRequest singleTrade = null;
for (ushort i = 0; i < inventory.Count; i++) { for (ushort i = 0; i < inventory.Count; i++) {
if (i % Trading.MaxItemsPerTrade == 0) { if (i % Trading.MaxItemsPerTrade == 0) {
if (trades.Count >= Trading.MaxTradesPerAccount) { if (trades.Count >= Trading.MaxTradesPerAccount) {
break; break;
} }
singleTrade = new SteamTradeOfferRequest(); singleTrade = new Steam.TradeOfferRequest();
trades.Add(singleTrade); trades.Add(singleTrade);
} }
SteamItem item = inventory[i]; Steam.Item item = inventory[i];
singleTrade.me.assets.Add(new SteamItem() { singleTrade.me.assets.Add(new Steam.Item() {
appid = "753", appid = "753",
contextid = "6", contextid = "6",
amount = item.amount, amount = item.amount,
@@ -357,14 +365,14 @@ namespace ArchiSteamFarm {
string referer = "https://steamcommunity.com/tradeoffer/new"; string referer = "https://steamcommunity.com/tradeoffer/new";
string request = referer + "/send"; string request = referer + "/send";
foreach (SteamTradeOfferRequest trade in trades) { foreach (Steam.TradeOfferRequest trade in trades) {
Dictionary<string, string> data = new Dictionary<string, string>(6) { Dictionary<string, string> data = new Dictionary<string, string>(6) {
{"sessionid", sessionID}, {"sessionid", sessionID},
{"serverid", "1"}, {"serverid", "1"},
{"partner", partnerID.ToString()}, {"partner", partnerID.ToString()},
{"tradeoffermessage", "Sent by ASF"}, {"tradeoffermessage", "Sent by ASF"},
{"json_tradeoffer", JsonConvert.SerializeObject(trade)}, {"json_tradeoffer", JsonConvert.SerializeObject(trade)},
{"trade_offer_create_params", string.IsNullOrEmpty(token) ? "" : string.Format("{{ \"trade_offer_access_token\":\"{0}\" }}", token)} // TODO: This should be rewrote {"trade_offer_create_params", string.IsNullOrEmpty(token) ? "" : $"{{\"trade_offer_access_token\":\"{token}\"}}"}
}; };
HttpResponseMessage response = null; HttpResponseMessage response = null;
@@ -462,13 +470,20 @@ namespace ArchiSteamFarm {
} }
foreach (string setCookieValue in setCookieValues) { foreach (string setCookieValue in setCookieValues) {
if (setCookieValue.Contains("steamparental=")) { if (!setCookieValue.Contains("steamparental=")) {
string setCookie = setCookieValue.Substring(setCookieValue.IndexOf("steamparental=") + 14); continue;
setCookie = setCookie.Substring(0, setCookie.IndexOf(';'));
Cookie["steamparental"] = setCookie;
Logging.LogGenericInfo("Success!", Bot.BotName);
return;
} }
string setCookie = setCookieValue.Substring(setCookieValue.IndexOf("steamparental=", StringComparison.Ordinal) + 14);
int index = setCookie.IndexOf(';');
if (index > 0) {
setCookie = setCookie.Substring(0, index);
}
Cookie["steamparental"] = setCookie;
Logging.LogGenericInfo("Success!", Bot.BotName);
return;
} }
Logging.LogGenericWarning("Failed to unlock parental account!", Bot.BotName); Logging.LogGenericWarning("Failed to unlock parental account!", Bot.BotName);

View File

@@ -44,6 +44,7 @@ namespace ArchiSteamFarm {
private static readonly uint LoginID = MsgClientLogon.ObfuscationMask; // This must be the same for all ASF bots and all ASF processes private static readonly uint LoginID = MsgClientLogon.ObfuscationMask; // This must be the same for all ASF bots and all ASF processes
private readonly string SentryFile; private readonly string SentryFile;
private readonly Timer AcceptConfirmationsTimer;
private readonly Timer SendItemsTimer; private readonly Timer SendItemsTimer;
internal readonly string BotName; internal readonly string BotName;
@@ -66,20 +67,12 @@ namespace ArchiSteamFarm {
private bool LoggedInElsewhere = false; private bool LoggedInElsewhere = false;
private string AuthCode, TwoFactorAuth; private string AuthCode, TwoFactorAuth;
internal static string GetAnyBotName() { internal static async Task RefreshCMs(uint cellID) {
foreach (string botName in Bots.Keys) {
return botName;
}
return null;
}
internal static async Task RefreshCMs() {
bool initialized = false; bool initialized = false;
for (byte i = 0; i < 3 && !initialized; i++) { for (byte i = 0; i < 3 && !initialized; i++) {
try { try {
Logging.LogGenericInfo("Refreshing list of CMs..."); Logging.LogGenericInfo("Refreshing list of CMs...");
await SteamDirectory.Initialize().ConfigureAwait(false); await SteamDirectory.Initialize(cellID).ConfigureAwait(false);
initialized = true; initialized = true;
} catch (Exception e) { } catch (Exception e) {
Logging.LogGenericException(e); Logging.LogGenericException(e);
@@ -178,9 +171,28 @@ namespace ArchiSteamFarm {
BotDatabase = BotDatabase.Load(botPath + ".db"); BotDatabase = BotDatabase.Load(botPath + ".db");
SentryFile = botPath + ".bin"; SentryFile = botPath + ".bin";
// Support and convert SDA files
string maFilePath = botPath + ".maFile";
if (BotDatabase.SteamGuardAccount == null && File.Exists(maFilePath)) {
ImportAuthenticator(maFilePath);
}
// Initialize // Initialize
SteamClient = new SteamClient(); SteamClient = new SteamClient();
if (Program.GlobalConfig.Debug && !Debugging.NetHookAlreadyInitialized) {
try {
if (Directory.Exists(Program.DebugDirectory)) {
Directory.Delete(Program.DebugDirectory, true);
}
Directory.CreateDirectory(Program.DebugDirectory);
SteamClient.DebugNetworkListener = new NetHookNetworkListener(Program.DebugDirectory);
Debugging.NetHookAlreadyInitialized = true;
} catch (Exception e) {
Logging.LogGenericException(e, botName);
}
}
ArchiHandler = new ArchiHandler(); ArchiHandler = new ArchiHandler();
SteamClient.AddHandler(ArchiHandler); SteamClient.AddHandler(ArchiHandler);
@@ -213,6 +225,15 @@ namespace ArchiSteamFarm {
CardsFarmer = new CardsFarmer(this); CardsFarmer = new CardsFarmer(this);
Trading = new Trading(this); Trading = new Trading(this);
if (BotConfig.AcceptConfirmationsPeriod > 0 && AcceptConfirmationsTimer == null) {
AcceptConfirmationsTimer = new Timer(
async e => await AcceptAllConfirmations().ConfigureAwait(false),
null,
TimeSpan.FromMinutes(BotConfig.AcceptConfirmationsPeriod), // Delay
TimeSpan.FromMinutes(BotConfig.AcceptConfirmationsPeriod) // Period
);
}
if (BotConfig.SendTradePeriod > 0 && SendItemsTimer == null) { if (BotConfig.SendTradePeriod > 0 && SendItemsTimer == null) {
SendItemsTimer = new Timer( SendItemsTimer = new Timer(
async e => await ResponseSendTrade().ConfigureAwait(false), async e => await ResponseSendTrade().ConfigureAwait(false),
@@ -235,10 +256,17 @@ namespace ArchiSteamFarm {
return; return;
} }
await BotDatabase.SteamGuardAccount.RefreshSessionAsync().ConfigureAwait(false);
try { try {
foreach (Confirmation confirmation in await BotDatabase.SteamGuardAccount.FetchConfirmationsAsync().ConfigureAwait(false)) { if (!await BotDatabase.SteamGuardAccount.RefreshSessionAsync().ConfigureAwait(false)) {
return;
}
Confirmation[] confirmations = await BotDatabase.SteamGuardAccount.FetchConfirmationsAsync().ConfigureAwait(false);
if (confirmations == null) {
return;
}
foreach (Confirmation confirmation in confirmations) {
if (BotDatabase.SteamGuardAccount.AcceptConfirmation(confirmation)) { if (BotDatabase.SteamGuardAccount.AcceptConfirmation(confirmation)) {
Logging.LogGenericInfo("Accepting confirmation: Success!", BotName); Logging.LogGenericInfo("Accepting confirmation: Success!", BotName);
} else { } else {
@@ -249,12 +277,15 @@ namespace ArchiSteamFarm {
Logging.LogGenericWarning("Accepting confirmation: Failed!", BotName); Logging.LogGenericWarning("Accepting confirmation: Failed!", BotName);
Logging.LogGenericWarning("Confirmation could not be accepted because of invalid token exception", BotName); Logging.LogGenericWarning("Confirmation could not be accepted because of invalid token exception", BotName);
Logging.LogGenericWarning("If issue persists, consider removing and readding ASF 2FA", BotName); Logging.LogGenericWarning("If issue persists, consider removing and readding ASF 2FA", BotName);
} catch (Exception e) {
Logging.LogGenericException(e, BotName);
return;
} }
} }
internal void ResetGamesPlayed() { internal void ResetGamesPlayed() {
if (BotConfig.GamesPlayedWhileIdle.Contains(0)) { if (!string.IsNullOrEmpty(BotConfig.CustomGamePlayedWhileIdle)) {
ArchiHandler.PlayGames(0); ArchiHandler.PlayGame(BotConfig.CustomGamePlayedWhileIdle);
} else { } else {
ArchiHandler.PlayGames(BotConfig.GamesPlayedWhileIdle); ArchiHandler.PlayGames(BotConfig.GamesPlayedWhileIdle);
} }
@@ -290,22 +321,29 @@ namespace ArchiSteamFarm {
return Response2FA(); return Response2FA();
case "!2faoff": case "!2faoff":
return Response2FAOff(); return Response2FAOff();
case "!2faok":
return await Response2FAOK().ConfigureAwait(false);
case "!exit": case "!exit":
Program.Exit(); Program.Exit();
return null; return null;
case "!farm":
return await ResponseFarm().ConfigureAwait(false);
case "!loot":
return await ResponseSendTrade().ConfigureAwait(false);
case "!rejoinchat": case "!rejoinchat":
return ResponseRejoinChat(); return ResponseRejoinChat();
case "!restart": case "!restart":
Program.Restart(); Program.Restart();
return "Done"; return null;
case "!status": case "!status":
return ResponseStatus(); return ResponseStatus();
case "!statusall": case "!statusall":
return ResponseStatusAll(); return ResponseStatusAll();
case "!stop": case "!stop":
return ResponseStop(); return ResponseStop();
case "!loot": case "!update":
return await ResponseSendTrade().ConfigureAwait(false); await Program.CheckForUpdate().ConfigureAwait(false);
return "Done!";
default: default:
return "Unrecognized command: " + message; return "Unrecognized command: " + message;
} }
@@ -316,12 +354,18 @@ namespace ArchiSteamFarm {
return Response2FA(args[1]); return Response2FA(args[1]);
case "!2faoff": case "!2faoff":
return Response2FAOff(args[1]); return Response2FAOff(args[1]);
case "!2faok":
return await Response2FAOK(args[1]).ConfigureAwait(false);
case "!addlicense": case "!addlicense":
if (args.Length > 2) { if (args.Length > 2) {
return await ResponseAddLicense(args[1], args[2]).ConfigureAwait(false); return await ResponseAddLicense(args[1], args[2]).ConfigureAwait(false);
} else { } else {
return await ResponseAddLicense(BotName, args[1]).ConfigureAwait(false); return await ResponseAddLicense(BotName, args[1]).ConfigureAwait(false);
} }
case "!farm":
return await ResponseFarm(args[1]).ConfigureAwait(false);
case "!loot":
return await ResponseSendTrade(args[1]).ConfigureAwait(false);
case "!play": case "!play":
if (args.Length > 2) { if (args.Length > 2) {
return await ResponsePlay(args[1], args[2]).ConfigureAwait(false); return await ResponsePlay(args[1], args[2]).ConfigureAwait(false);
@@ -336,12 +380,10 @@ namespace ArchiSteamFarm {
} }
case "!start": case "!start":
return await ResponseStart(args[1]).ConfigureAwait(false); return await ResponseStart(args[1]).ConfigureAwait(false);
case "!stop":
return ResponseStop(args[1]);
case "!status": case "!status":
return ResponseStatus(args[1]); return ResponseStatus(args[1]);
case "!loot": case "!stop":
return await ResponseSendTrade(args[1]).ConfigureAwait(false); return ResponseStop(args[1]);
default: default:
return "Unrecognized command: " + args[0]; return "Unrecognized command: " + args[0];
} }
@@ -355,7 +397,7 @@ namespace ArchiSteamFarm {
if (!KeepRunning) { if (!KeepRunning) {
KeepRunning = true; KeepRunning = true;
var handleCallbacks = Task.Run(() => HandleCallbacks()); Task.Run(() => HandleCallbacks()).Forget();
} }
Logging.LogGenericInfo("Starting...", BotName); Logging.LogGenericInfo("Starting...", BotName);
@@ -384,6 +426,62 @@ namespace ArchiSteamFarm {
Program.OnBotShutdown(); Program.OnBotShutdown();
} }
private void ImportAuthenticator(string maFilePath) {
if (BotDatabase.SteamGuardAccount != null || !File.Exists(maFilePath)) {
return;
}
Logging.LogGenericInfo("Converting SDA .maFile into ASF format...", BotName);
try {
BotDatabase.SteamGuardAccount = JsonConvert.DeserializeObject<SteamGuardAccount>(File.ReadAllText(maFilePath));
File.Delete(maFilePath);
Logging.LogGenericInfo("Success!", BotName);
} catch (Exception e) {
Logging.LogGenericException(e, BotName);
return;
}
// If this is SDA file, then we should already have everything ready
if (BotDatabase.SteamGuardAccount.Session != null) {
Logging.LogGenericInfo("Successfully finished importing mobile authenticator!", BotName);
return;
}
// But here we're dealing with WinAuth authenticator
Logging.LogGenericInfo("ASF requires a few more steps to complete authenticator import...", BotName);
InitializeLoginAndPassword();
UserLogin userLogin = new UserLogin(BotConfig.SteamLogin, BotConfig.SteamPassword);
LoginResult loginResult;
while ((loginResult = userLogin.DoLogin()) != LoginResult.LoginOkay) {
switch (loginResult) {
case LoginResult.Need2FA:
userLogin.TwoFactorCode = Program.GetUserInput(BotName, Program.EUserInputType.TwoFactorAuthentication);
break;
default:
Logging.LogGenericError("Unhandled situation: " + loginResult, BotName);
return;
}
}
if (userLogin.Session == null) {
BotDatabase.SteamGuardAccount = null;
Logging.LogGenericError("Session is invalid, linking can't be completed!", BotName);
return;
}
BotDatabase.SteamGuardAccount.FullyEnrolled = true;
BotDatabase.SteamGuardAccount.Session = userLogin.Session;
if (string.IsNullOrEmpty(BotDatabase.SteamGuardAccount.DeviceID)) {
BotDatabase.SteamGuardAccount.DeviceID = Program.GetUserInput(BotName, Program.EUserInputType.DeviceID);
}
BotDatabase.Save();
Logging.LogGenericInfo("Successfully finished importing mobile authenticator!", BotName);
}
private string ResponseStatus() { private string ResponseStatus() {
if (CardsFarmer.CurrentGamesFarming.Count > 0) { if (CardsFarmer.CurrentGamesFarming.Count > 0) {
return "Bot " + BotName + " is currently farming appIDs: " + string.Join(", ", CardsFarmer.CurrentGamesFarming) + " and has a total of " + CardsFarmer.GamesToFarm.Count + " games left to farm."; return "Bot " + BotName + " is currently farming appIDs: " + string.Join(", ", CardsFarmer.CurrentGamesFarming) + " and has a total of " + CardsFarmer.GamesToFarm.Count + " games left to farm.";
@@ -428,7 +526,7 @@ namespace ArchiSteamFarm {
} }
await Trading.LimitInventoryRequestsAsync().ConfigureAwait(false); await Trading.LimitInventoryRequestsAsync().ConfigureAwait(false);
List<SteamItem> inventory = await ArchiWebHandler.GetMyTradableInventory().ConfigureAwait(false); List<Steam.Item> inventory = await ArchiWebHandler.GetMyTradableInventory().ConfigureAwait(false);
if (inventory == null || inventory.Count == 0) { if (inventory == null || inventory.Count == 0) {
return "Nothing to send, inventory seems empty!"; return "Nothing to send, inventory seems empty!";
@@ -502,6 +600,46 @@ namespace ArchiSteamFarm {
return bot.Response2FAOff(); return bot.Response2FAOff();
} }
private async Task<string> Response2FAOK() {
if (BotDatabase.SteamGuardAccount == null) {
return "That bot doesn't have ASF 2FA enabled!";
}
await AcceptAllConfirmations().ConfigureAwait(false);
return "Done!";
}
private static async Task<string> Response2FAOK(string botName) {
if (string.IsNullOrEmpty(botName)) {
return null;
}
Bot bot;
if (!Bots.TryGetValue(botName, out bot)) {
return "Couldn't find any bot named " + botName + "!";
}
return await bot.Response2FAOK().ConfigureAwait(false);
}
private async Task<string> ResponseFarm() {
await CardsFarmer.RestartFarming().ConfigureAwait(false);
return "Done!";
}
private static async Task<string> ResponseFarm(string botName) {
if (string.IsNullOrEmpty(botName)) {
return null;
}
Bot bot;
if (!Bots.TryGetValue(botName, out bot)) {
return "Couldn't find any bot named " + botName + "!";
}
return await bot.ResponseFarm().ConfigureAwait(false);
}
private async Task<string> ResponseRedeem(string message, bool validate) { private async Task<string> ResponseRedeem(string message, bool validate) {
if (string.IsNullOrEmpty(message)) { if (string.IsNullOrEmpty(message)) {
return null; return null;
@@ -822,20 +960,22 @@ namespace ArchiSteamFarm {
return; return;
} }
// TODO: I really need something better if (new SteamID(steamID).IsChatAccount) {
if (steamID < 110300000000000000) {
SteamFriends.SendChatMessage(steamID, EChatEntryType.ChatMsg, message);
} else {
SteamFriends.SendChatRoomMessage(steamID, EChatEntryType.ChatMsg, message); SteamFriends.SendChatRoomMessage(steamID, EChatEntryType.ChatMsg, message);
} else {
SteamFriends.SendChatMessage(steamID, EChatEntryType.ChatMsg, message);
} }
} }
private bool LinkMobileAuthenticator() { private void LinkMobileAuthenticator() {
if (BotDatabase.SteamGuardAccount != null) { if (BotDatabase.SteamGuardAccount != null) {
return false; return;
} }
Logging.LogGenericInfo("Linking new ASF MobileAuthenticator...", BotName); Logging.LogGenericInfo("Linking new ASF MobileAuthenticator...", BotName);
InitializeLoginAndPassword();
UserLogin userLogin = new UserLogin(BotConfig.SteamLogin, BotConfig.SteamPassword); UserLogin userLogin = new UserLogin(BotConfig.SteamLogin, BotConfig.SteamPassword);
LoginResult loginResult; LoginResult loginResult;
while ((loginResult = userLogin.DoLogin()) != LoginResult.LoginOkay) { while ((loginResult = userLogin.DoLogin()) != LoginResult.LoginOkay) {
@@ -845,7 +985,7 @@ namespace ArchiSteamFarm {
break; break;
default: default:
Logging.LogGenericError("Unhandled situation: " + loginResult, BotName); Logging.LogGenericError("Unhandled situation: " + loginResult, BotName);
return false; return;
} }
} }
@@ -859,7 +999,7 @@ namespace ArchiSteamFarm {
break; break;
default: default:
Logging.LogGenericError("Unhandled situation: " + linkResult, BotName); Logging.LogGenericError("Unhandled situation: " + linkResult, BotName);
return false; return;
} }
} }
@@ -869,12 +1009,11 @@ namespace ArchiSteamFarm {
if (finalizeResult != AuthenticatorLinker.FinalizeResult.Success) { if (finalizeResult != AuthenticatorLinker.FinalizeResult.Success) {
Logging.LogGenericError("Unhandled situation: " + finalizeResult, BotName); Logging.LogGenericError("Unhandled situation: " + finalizeResult, BotName);
DelinkMobileAuthenticator(); DelinkMobileAuthenticator();
return false; return;
} }
Logging.LogGenericInfo("Successfully linked ASF as new mobile authenticator for this account!", BotName); Logging.LogGenericInfo("Successfully linked ASF as new mobile authenticator for this account!", BotName);
Program.GetUserInput(BotName, Program.EUserInputType.RevocationCode, BotDatabase.SteamGuardAccount.RevocationCode); Program.GetUserInput(BotName, Program.EUserInputType.RevocationCode, BotDatabase.SteamGuardAccount.RevocationCode);
return true;
} }
private bool DelinkMobileAuthenticator() { private bool DelinkMobileAuthenticator() {
@@ -899,6 +1038,16 @@ namespace ArchiSteamFarm {
SteamFriends.JoinChat(BotConfig.SteamMasterClanID); SteamFriends.JoinChat(BotConfig.SteamMasterClanID);
} }
private void InitializeLoginAndPassword() {
if (string.IsNullOrEmpty(BotConfig.SteamLogin)) {
BotConfig.SteamLogin = Program.GetUserInput(BotName, Program.EUserInputType.Login);
}
if (string.IsNullOrEmpty(BotConfig.SteamPassword) && string.IsNullOrEmpty(BotDatabase.LoginKey)) {
BotConfig.SteamPassword = Program.GetUserInput(BotName, Program.EUserInputType.Password);
}
}
private void OnConnected(SteamClient.ConnectedCallback callback) { private void OnConnected(SteamClient.ConnectedCallback callback) {
if (callback == null) { if (callback == null) {
return; return;
@@ -927,13 +1076,7 @@ namespace ArchiSteamFarm {
} }
} }
if (string.IsNullOrEmpty(BotConfig.SteamLogin)) { InitializeLoginAndPassword();
BotConfig.SteamLogin = Program.GetUserInput(BotName, Program.EUserInputType.Login);
}
if (string.IsNullOrEmpty(BotConfig.SteamPassword) && string.IsNullOrEmpty(BotDatabase.LoginKey)) {
BotConfig.SteamPassword = Program.GetUserInput(BotName, Program.EUserInputType.Password);
}
SteamUser.LogOn(new SteamUser.LogOnDetails { SteamUser.LogOn(new SteamUser.LogOnDetails {
Username = BotConfig.SteamLogin, Username = BotConfig.SteamLogin,
@@ -975,8 +1118,8 @@ namespace ArchiSteamFarm {
} }
} else if (LoggedInElsewhere) { } else if (LoggedInElsewhere) {
LoggedInElsewhere = false; LoggedInElsewhere = false;
Logging.LogGenericWarning("Account is being used elsewhere, will try reconnecting in 30 minutes...", BotName); Logging.LogGenericInfo("Account is being used elsewhere, ASF will try to resume farming in " + Program.GlobalConfig.AccountPlayingDelay + " minutes...", BotName);
await Utilities.SleepAsync(30 * 60 * 1000).ConfigureAwait(false); await Utilities.SleepAsync(Program.GlobalConfig.AccountPlayingDelay * 60 * 1000).ConfigureAwait(false);
} }
Logging.LogGenericInfo("Reconnecting...", BotName); Logging.LogGenericInfo("Reconnecting...", BotName);
@@ -1152,6 +1295,16 @@ namespace ArchiSteamFarm {
case EResult.OK: case EResult.OK:
Logging.LogGenericInfo("Successfully logged on!", BotName); Logging.LogGenericInfo("Successfully logged on!", BotName);
if (callback.CellID != 0) {
Program.GlobalDatabase.CellID = callback.CellID;
}
// Support and convert SDA files
string maFilePath = Path.Combine(Program.ConfigDirectory, callback.ClientSteamID.ConvertToUInt64() + ".maFile");
if (BotDatabase.SteamGuardAccount == null && File.Exists(maFilePath)) {
ImportAuthenticator(maFilePath);
}
if (BotConfig.UseAsfAsMobileAuthenticator && TwoFactorAuth == null && BotDatabase.SteamGuardAccount == null) { if (BotConfig.UseAsfAsMobileAuthenticator && TwoFactorAuth == null && BotDatabase.SteamGuardAccount == null) {
LinkMobileAuthenticator(); LinkMobileAuthenticator();
} }
@@ -1171,19 +1324,23 @@ namespace ArchiSteamFarm {
return; return;
} }
if (BotConfig.DismissInventoryNotifications) {
await ArchiWebHandler.MarkInventory().ConfigureAwait(false);
}
if (BotConfig.SteamMasterClanID != 0) { if (BotConfig.SteamMasterClanID != 0) {
await ArchiWebHandler.JoinClan(BotConfig.SteamMasterClanID).ConfigureAwait(false); await ArchiWebHandler.JoinClan(BotConfig.SteamMasterClanID).ConfigureAwait(false);
JoinMasterChat(); JoinMasterChat();
} }
if (BotConfig.Statistics) { if (Program.GlobalConfig.Statistics) {
await ArchiWebHandler.JoinClan(ArchiSCFarmGroup).ConfigureAwait(false); await ArchiWebHandler.JoinClan(ArchiSCFarmGroup).ConfigureAwait(false);
SteamFriends.JoinChat(ArchiSCFarmGroup); SteamFriends.JoinChat(ArchiSCFarmGroup);
} }
Trading.CheckTrades(); Trading.CheckTrades();
var start = Task.Run(async () => await CardsFarmer.StartFarming().ConfigureAwait(false)); Task.Run(async () => await CardsFarmer.StartFarming().ConfigureAwait(false)).Forget();
break; break;
case EResult.NoConnection: case EResult.NoConnection:
case EResult.ServiceUnavailable: case EResult.ServiceUnavailable:
@@ -1283,14 +1440,14 @@ namespace ArchiSteamFarm {
SteamFriends.RequestOfflineMessages(); SteamFriends.RequestOfflineMessages();
} }
private async void OnPurchaseResponse(ArchiHandler.PurchaseResponseCallback callback) { private void OnPurchaseResponse(ArchiHandler.PurchaseResponseCallback callback) {
if (callback == null) { if (callback == null) {
return; return;
} }
if (callback.PurchaseResult == ArchiHandler.PurchaseResponseCallback.EPurchaseResult.OK) { if (callback.PurchaseResult == ArchiHandler.PurchaseResponseCallback.EPurchaseResult.OK) {
// We will restart CF module to recalculate current status and decide about new optimal approach // We will restart CF module to recalculate current status and decide about new optimal approach
await CardsFarmer.RestartFarming().ConfigureAwait(false); Task.Run(async () => await CardsFarmer.RestartFarming().ConfigureAwait(false)).Forget();
} }
} }
} }

View File

@@ -30,8 +30,6 @@ using System.Xml;
namespace ArchiSteamFarm { namespace ArchiSteamFarm {
internal sealed class BotConfig { internal sealed class BotConfig {
internal static readonly HashSet<uint> GlobalBlacklist = new HashSet<uint> { 303700, 335590, 368020, 425280 };
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
internal bool Enabled { get; private set; } = false; internal bool Enabled { get; private set; } = false;
@@ -90,10 +88,13 @@ namespace ArchiSteamFarm {
internal byte SendTradePeriod { get; private set; } = 0; internal byte SendTradePeriod { get; private set; } = 0;
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
internal HashSet<uint> GamesPlayedWhileIdle { get; private set; } = new HashSet<uint>() { 0 }; internal byte AcceptConfirmationsPeriod { get; private set; } = 0;
[JsonProperty]
internal string CustomGamePlayedWhileIdle { get; private set; } = null;
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
internal bool Statistics { get; private set; } = true; internal HashSet<uint> GamesPlayedWhileIdle { get; private set; } = new HashSet<uint>() { 0 };
internal static BotConfig Load(string path) { internal static BotConfig Load(string path) {
@@ -199,8 +200,6 @@ namespace ArchiSteamFarm {
} }
break; break;
case "Statistics": case "Statistics":
botConfig.Statistics = bool.Parse(value);
break;
case "Blacklist": case "Blacklist":
case "SteamNickname": case "SteamNickname":
break; break;

View File

@@ -34,6 +34,10 @@ namespace ArchiSteamFarm {
return _LoginKey; return _LoginKey;
} }
set { set {
if (_LoginKey == value) {
return;
}
_LoginKey = value; _LoginKey = value;
Save(); Save();
} }
@@ -44,6 +48,10 @@ namespace ArchiSteamFarm {
return _SteamGuardAccount; return _SteamGuardAccount;
} }
set { set {
if (_SteamGuardAccount == value) {
return;
}
_SteamGuardAccount = value; _SteamGuardAccount = value;
Save(); Save();
} }
@@ -83,7 +91,7 @@ namespace ArchiSteamFarm {
// This constructor is used only by deserializer // This constructor is used only by deserializer
private BotDatabase() { } private BotDatabase() { }
private void Save() { internal void Save() {
lock (FilePath) { lock (FilePath) {
try { try {
File.WriteAllText(FilePath, JsonConvert.SerializeObject(this)); File.WriteAllText(FilePath, JsonConvert.SerializeObject(this));

View File

@@ -27,17 +27,15 @@ using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace ArchiSteamFarm { namespace ArchiSteamFarm {
internal sealed class CardsFarmer { internal sealed class CardsFarmer {
private const byte StatusCheckSleep = 5; // In minutes, how long to wait before checking the appID again
private const ushort MaxFarmingTime = 600; // In minutes, how long ASF is allowed to farm one game in solo mode
internal readonly ConcurrentDictionary<uint, float> GamesToFarm = new ConcurrentDictionary<uint, float>(); internal readonly ConcurrentDictionary<uint, float> GamesToFarm = new ConcurrentDictionary<uint, float>();
internal readonly List<uint> CurrentGamesFarming = new List<uint>(); internal readonly HashSet<uint> CurrentGamesFarming = new HashSet<uint>();
private readonly ManualResetEvent FarmResetEvent = new ManualResetEvent(false); private readonly ManualResetEvent FarmResetEvent = new ManualResetEvent(false);
private readonly SemaphoreSlim Semaphore = new SemaphoreSlim(1); private readonly SemaphoreSlim Semaphore = new SemaphoreSlim(1);
@@ -49,22 +47,28 @@ namespace ArchiSteamFarm {
private bool NowFarming = false; private bool NowFarming = false;
internal CardsFarmer(Bot bot) { internal CardsFarmer(Bot bot) {
if (bot == null) {
return;
}
Bot = bot; Bot = bot;
Timer = new Timer( if (Program.GlobalConfig.IdleFarmingPeriod > 0 && Timer == null) {
async e => await CheckGamesForFarming().ConfigureAwait(false), Timer = new Timer(
null, async e => await CheckGamesForFarming().ConfigureAwait(false),
TimeSpan.FromMinutes(15), // Delay null,
TimeSpan.FromMinutes(60) // Period TimeSpan.FromHours(Program.GlobalConfig.IdleFarmingPeriod), // Delay
); TimeSpan.FromHours(Program.GlobalConfig.IdleFarmingPeriod) // Period
);
}
} }
internal static List<uint> GetGamesToFarmSolo(ConcurrentDictionary<uint, float> gamesToFarm) { internal static HashSet<uint> GetGamesToFarmSolo(ConcurrentDictionary<uint, float> gamesToFarm) {
if (gamesToFarm == null) { if (gamesToFarm == null) {
return null; return null;
} }
List<uint> result = new List<uint>(); HashSet<uint> result = new HashSet<uint>();
foreach (KeyValuePair<uint, float> keyValue in gamesToFarm) { foreach (KeyValuePair<uint, float> keyValue in gamesToFarm) {
if (keyValue.Value >= 2) { if (keyValue.Value >= 2) {
result.Add(keyValue.Key); result.Add(keyValue.Key);
@@ -79,11 +83,7 @@ namespace ArchiSteamFarm {
return 0; return 0;
} }
foreach (uint appID in gamesToFarm.Keys) { return gamesToFarm.Keys.FirstOrDefault();
return appID;
}
return 0;
} }
internal async Task<bool> SwitchToManualMode(bool manualMode) { internal async Task<bool> SwitchToManualMode(bool manualMode) {
@@ -98,7 +98,7 @@ namespace ArchiSteamFarm {
await StopFarming().ConfigureAwait(false); await StopFarming().ConfigureAwait(false);
} else { } else {
Logging.LogGenericInfo("Now running in Automatic Farming mode", Bot.BotName); Logging.LogGenericInfo("Now running in Automatic Farming mode", Bot.BotName);
var start = Task.Run(async () => await StartFarming().ConfigureAwait(false)); Task.Run(async () => await StartFarming().ConfigureAwait(false)).Forget();
} }
return true; return true;
@@ -110,7 +110,6 @@ namespace ArchiSteamFarm {
} }
float maxHour = 0; float maxHour = 0;
foreach (float hour in appIDs.Values) { foreach (float hour in appIDs.Values) {
if (hour > maxHour) { if (hour > maxHour) {
maxHour = hour; maxHour = hour;
@@ -168,6 +167,7 @@ namespace ArchiSteamFarm {
if (!await IsAnythingToFarm().ConfigureAwait(false)) { if (!await IsAnythingToFarm().ConfigureAwait(false)) {
Semaphore.Release(); // We have nothing to do, don't forget to release semaphore Semaphore.Release(); // We have nothing to do, don't forget to release semaphore
Logging.LogGenericInfo("We don't have anything to farm on this account!", Bot.BotName);
return; return;
} }
@@ -181,10 +181,10 @@ namespace ArchiSteamFarm {
if (Bot.BotConfig.CardDropsRestricted) { // If we have restricted card drops, we use complex algorithm if (Bot.BotConfig.CardDropsRestricted) { // If we have restricted card drops, we use complex algorithm
Logging.LogGenericInfo("Chosen farming algorithm: Complex", Bot.BotName); Logging.LogGenericInfo("Chosen farming algorithm: Complex", Bot.BotName);
while (GamesToFarm.Count > 0) { while (GamesToFarm.Count > 0) {
List<uint> gamesToFarmSolo = GetGamesToFarmSolo(GamesToFarm); HashSet<uint> gamesToFarmSolo = GetGamesToFarmSolo(GamesToFarm);
if (gamesToFarmSolo.Count > 0) { if (gamesToFarmSolo.Count > 0) {
while (gamesToFarmSolo.Count > 0) { while (gamesToFarmSolo.Count > 0) {
uint appID = gamesToFarmSolo[0]; uint appID = gamesToFarmSolo.First();
if (await FarmSolo(appID).ConfigureAwait(false)) { if (await FarmSolo(appID).ConfigureAwait(false)) {
farmedSomething = true; farmedSomething = true;
Logging.LogGenericInfo("Done farming: " + appID, Bot.BotName); Logging.LogGenericInfo("Done farming: " + appID, Bot.BotName);
@@ -197,7 +197,6 @@ namespace ArchiSteamFarm {
} }
} else { } else {
if (FarmMultiple(GamesToFarm)) { if (FarmMultiple(GamesToFarm)) {
farmedSomething = true;
Logging.LogGenericInfo("Done farming: " + string.Join(", ", GamesToFarm.Keys), Bot.BotName); Logging.LogGenericInfo("Done farming: " + string.Join(", ", GamesToFarm.Keys), Bot.BotName);
} else { } else {
NowFarming = false; NowFarming = false;
@@ -222,6 +221,14 @@ namespace ArchiSteamFarm {
CurrentGamesFarming.Clear(); CurrentGamesFarming.Clear();
CurrentGamesFarming.TrimExcess(); CurrentGamesFarming.TrimExcess();
NowFarming = false; NowFarming = false;
// We finished our queue for now, make sure that everything is indeed farmed before proceeding further
// Some games could be added in the meantime
if (await IsAnythingToFarm().ConfigureAwait(false)) {
Task.Run(async () => await StartFarming().ConfigureAwait(false)).Forget();
return;
}
Logging.LogGenericInfo("Farming finished!", Bot.BotName); Logging.LogGenericInfo("Farming finished!", Bot.BotName);
await Bot.OnFarmingFinished(farmedSomething).ConfigureAwait(false); await Bot.OnFarmingFinished(farmedSomething).ConfigureAwait(false);
} }
@@ -247,7 +254,7 @@ namespace ArchiSteamFarm {
private async Task<bool> IsAnythingToFarm() { private async Task<bool> IsAnythingToFarm() {
if (NowFarming) { if (NowFarming) {
return false; return true;
} }
if (await Bot.ArchiWebHandler.ReconnectIfNeeded().ConfigureAwait(false)) { if (await Bot.ArchiWebHandler.ReconnectIfNeeded().ConfigureAwait(false)) {
@@ -290,12 +297,12 @@ namespace ArchiSteamFarm {
await Task.WhenAll(tasks).ConfigureAwait(false); await Task.WhenAll(tasks).ConfigureAwait(false);
if (GamesToFarm.Count == 0) { if (GamesToFarm.Count == 0) {
return true; return false;
} }
// If we have restricted card drops, actually do check hours of all games that are left to farm // If we have restricted card drops, actually do check hours of all games that are left to farm
if (Bot.BotConfig.CardDropsRestricted) { if (Bot.BotConfig.CardDropsRestricted) {
tasks = new List<Task>(GamesToFarm.Keys.Count); tasks = new List<Task>(GamesToFarm.Count);
Logging.LogGenericInfo("Checking hours...", Bot.BotName); Logging.LogGenericInfo("Checking hours...", Bot.BotName);
foreach (uint appID in GamesToFarm.Keys) { foreach (uint appID in GamesToFarm.Keys) {
tasks.Add(Task.Run(async () => await CheckHours(appID).ConfigureAwait(false))); tasks.Add(Task.Run(async () => await CheckHours(appID).ConfigureAwait(false)));
@@ -318,7 +325,7 @@ namespace ArchiSteamFarm {
foreach (HtmlNode htmlNode in htmlNodeCollection) { foreach (HtmlNode htmlNode in htmlNodeCollection) {
string steamLink = htmlNode.GetAttributeValue("href", null); string steamLink = htmlNode.GetAttributeValue("href", null);
if (steamLink == null) { if (string.IsNullOrEmpty(steamLink)) {
continue; continue;
} }
@@ -327,7 +334,7 @@ namespace ArchiSteamFarm {
continue; continue;
} }
if (BotConfig.GlobalBlacklist.Contains(appID)) { if (GlobalConfig.GlobalBlacklist.Contains(appID) || Program.GlobalConfig.Blacklist.Contains(appID)) {
continue; continue;
} }
@@ -385,7 +392,7 @@ namespace ArchiSteamFarm {
} }
private async Task CheckGamesForFarming() { private async Task CheckGamesForFarming() {
if (NowFarming || ManualMode || GamesToFarm.Count > 0 || !Bot.SteamClient.IsConnected) { if (NowFarming || ManualMode || !Bot.SteamClient.IsConnected) {
return; return;
} }
@@ -417,9 +424,9 @@ namespace ArchiSteamFarm {
bool success = true; bool success = true;
bool? keepFarming = await ShouldFarm(appID).ConfigureAwait(false); bool? keepFarming = await ShouldFarm(appID).ConfigureAwait(false);
for (ushort farmingTime = 0; farmingTime <= MaxFarmingTime && (!keepFarming.HasValue || keepFarming.Value); farmingTime += StatusCheckSleep) { for (ushort farmingTime = 0; farmingTime <= 60 * Program.GlobalConfig.MaxFarmingTime && keepFarming.GetValueOrDefault(true); farmingTime += Program.GlobalConfig.FarmingDelay) {
Logging.LogGenericInfo("Still farming: " + appID, Bot.BotName); Logging.LogGenericInfo("Still farming: " + appID, Bot.BotName);
if (FarmResetEvent.WaitOne(1000 * 60 * StatusCheckSleep)) { if (FarmResetEvent.WaitOne(60 * 1000 * Program.GlobalConfig.FarmingDelay)) {
success = false; success = false;
break; break;
} }
@@ -441,13 +448,13 @@ namespace ArchiSteamFarm {
bool success = true; bool success = true;
while (maxHour < 2) { while (maxHour < 2) {
Logging.LogGenericInfo("Still farming: " + string.Join(", ", appIDs), Bot.BotName); Logging.LogGenericInfo("Still farming: " + string.Join(", ", appIDs), Bot.BotName);
if (FarmResetEvent.WaitOne(1000 * 60 * StatusCheckSleep)) { if (FarmResetEvent.WaitOne(60 * 1000 * Program.GlobalConfig.FarmingDelay)) {
success = false; success = false;
break; break;
} }
// Don't forget to update our GamesToFarm hours // Don't forget to update our GamesToFarm hours
float timePlayed = StatusCheckSleep / 60.0F; float timePlayed = Program.GlobalConfig.FarmingDelay / 60.0F;
foreach (KeyValuePair<uint, float> gameToFarm in GamesToFarm) { foreach (KeyValuePair<uint, float> gameToFarm in GamesToFarm) {
if (!appIDs.Contains(gameToFarm.Key)) { if (!appIDs.Contains(gameToFarm.Key)) {
continue; continue;

View File

@@ -31,5 +31,7 @@ namespace ArchiSteamFarm {
#endif #endif
internal static bool IsReleaseBuild => !IsDebugBuild; internal static bool IsReleaseBuild => !IsDebugBuild;
internal static bool NetHookAlreadyInitialized { get; set; } = false;
} }
} }

View File

@@ -0,0 +1,103 @@
/*
_ _ _ ____ _ _____
/ \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
/ _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
Copyright 2015-2016 Łukasz "JustArchi" Domeradzki
Contact: JustArchi@JustArchi.net
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
namespace ArchiSteamFarm {
internal sealed class GlobalConfig {
internal enum EUpdateChannel : byte {
Unknown,
Stable,
Experimental
}
// This is hardcoded blacklist which should not be possible to change
internal static readonly HashSet<uint> GlobalBlacklist = new HashSet<uint> { 267420, 303700, 335590, 368020, 425280 };
[JsonProperty(Required = Required.DisallowNull)]
internal bool Debug { get; private set; } = false;
[JsonProperty(Required = Required.DisallowNull)]
internal bool AutoUpdates { get; private set; } = true;
[JsonProperty(Required = Required.DisallowNull)]
internal EUpdateChannel UpdateChannel { get; private set; } = EUpdateChannel.Stable;
[JsonProperty(Required = Required.DisallowNull)]
internal byte MaxFarmingTime { get; private set; } = 10;
[JsonProperty(Required = Required.DisallowNull)]
internal byte IdleFarmingPeriod { get; private set; } = 3;
[JsonProperty(Required = Required.DisallowNull)]
internal byte FarmingDelay { get; private set; } = 5;
[JsonProperty(Required = Required.DisallowNull)]
internal byte AccountPlayingDelay { get; private set; } = 5;
[JsonProperty(Required = Required.DisallowNull)]
internal byte LoginLimiterDelay { get; private set; } = 7;
[JsonProperty(Required = Required.DisallowNull)]
internal byte InventoryLimiterDelay { get; private set; } = 3;
[JsonProperty(Required = Required.DisallowNull)]
internal byte HttpTimeout { get; private set; } = 30;
[JsonProperty(Required = Required.DisallowNull)]
internal string WCFHostname { get; private set; } = "localhost";
[JsonProperty(Required = Required.DisallowNull)]
internal ushort WCFPort { get; private set; } = 1242;
[JsonProperty(Required = Required.DisallowNull)]
internal bool Statistics { get; private set; } = true;
[JsonProperty(Required = Required.DisallowNull)]
internal HashSet<uint> Blacklist { get; private set; } = new HashSet<uint>(GlobalBlacklist);
internal static GlobalConfig Load() {
string filePath = Path.Combine(Program.ConfigDirectory, Program.GlobalConfigFile);
if (!File.Exists(filePath)) {
return null;
}
GlobalConfig globalConfig;
try {
globalConfig = JsonConvert.DeserializeObject<GlobalConfig>(File.ReadAllText(filePath));
} catch (Exception e) {
Logging.LogGenericException(e);
return null;
}
return globalConfig;
}
// This constructor is used only by deserializer
private GlobalConfig() { }
}
}

View File

@@ -23,33 +23,57 @@
*/ */
using Newtonsoft.Json; using Newtonsoft.Json;
using System;
using System.IO;
namespace ArchiSteamFarm { namespace ArchiSteamFarm {
internal sealed class SteamItem { internal sealed class GlobalDatabase {
// REF: https://developer.valvesoftware.com/wiki/Steam_Web_API/IEconService#CEcon_Asset private static readonly string FilePath = Path.Combine(Program.ConfigDirectory, Program.GlobalDatabaseFile);
[JsonProperty(Required = Required.DisallowNull)] internal uint CellID {
internal string appid { get; set; } get {
return _CellID;
}
set {
if (_CellID == value) {
return;
}
[JsonProperty(Required = Required.DisallowNull)] _CellID = value;
internal string contextid { get; set; } Save();
}
[JsonProperty(Required = Required.DisallowNull)]
internal string assetid { get; set; }
[JsonProperty(Required = Required.DisallowNull)]
internal string id {
get { return assetid; }
set { assetid = value; }
} }
[JsonProperty(Required = Required.AllowNull)] [JsonProperty(Required = Required.DisallowNull)]
internal string classid { get; set; } private uint _CellID = 0;
[JsonProperty(Required = Required.AllowNull)] internal static GlobalDatabase Load() {
internal string instanceid { get; set; } if (!File.Exists(FilePath)) {
return new GlobalDatabase();
}
[JsonProperty(Required = Required.Always)] GlobalDatabase globalDatabase;
internal string amount { get; set; } try {
globalDatabase = JsonConvert.DeserializeObject<GlobalDatabase>(File.ReadAllText(FilePath));
} catch (Exception e) {
Logging.LogGenericException(e);
return null;
}
return globalDatabase;
}
// This constructor is used only by deserializer
private GlobalDatabase() { }
private void Save() {
lock (FilePath) {
try {
File.WriteAllText(FilePath, JsonConvert.SerializeObject(this));
} catch (Exception e) {
Logging.LogGenericException(e);
}
}
}
} }
} }

View File

@@ -26,8 +26,21 @@ using Newtonsoft.Json;
using System.Collections.Generic; using System.Collections.Generic;
namespace ArchiSteamFarm { namespace ArchiSteamFarm {
internal sealed class SteamItemList { internal static class GitHub {
[JsonProperty(Required = Required.Always)] internal sealed class Asset {
internal List<SteamItem> assets { get; } = new List<SteamItem>(); [JsonProperty(PropertyName = "name", Required = Required.Always)]
internal string Name { get; private set; }
[JsonProperty(PropertyName = "browser_download_url", Required = Required.Always)]
internal string DownloadURL { get; private set; }
}
internal sealed class ReleaseResponse {
[JsonProperty(PropertyName = "tag_name", Required = Required.Always)]
internal string Tag { get; private set; }
[JsonProperty(PropertyName = "assets", Required = Required.Always)]
internal List<Asset> Assets { get; private set; }
}
} }
} }

View File

@@ -0,0 +1,122 @@
/*
_ _ _ ____ _ _____
/ \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
/ _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
Copyright 2015-2016 Łukasz "JustArchi" Domeradzki
Contact: JustArchi@JustArchi.net
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
using Newtonsoft.Json;
using SteamKit2;
using System.Collections.Generic;
namespace ArchiSteamFarm {
internal static class Steam {
internal sealed class Item {
// REF: https://developer.valvesoftware.com/wiki/Steam_Web_API/IEconService#CEcon_Asset
[JsonProperty(Required = Required.DisallowNull)]
internal string appid { get; set; }
[JsonProperty(Required = Required.DisallowNull)]
internal string contextid { get; set; }
[JsonProperty(Required = Required.DisallowNull)]
internal string assetid { get; set; }
[JsonProperty(Required = Required.DisallowNull)]
internal string id {
get { return assetid; }
set { assetid = value; }
}
[JsonProperty(Required = Required.AllowNull)]
internal string classid { get; set; }
[JsonProperty(Required = Required.AllowNull)]
internal string instanceid { get; set; }
[JsonProperty(Required = Required.Always)]
internal string amount { get; set; }
}
internal sealed class ItemList {
[JsonProperty(Required = Required.Always)]
internal List<Steam.Item> assets { get; } = new List<Steam.Item>();
}
internal sealed class TradeOffer {
// REF: https://developer.valvesoftware.com/wiki/Steam_Web_API/IEconService#CEcon_TradeOffer
internal enum ETradeOfferState : byte {
Unknown,
Invalid,
Active,
Accepted,
Countered,
Expired,
Canceled,
Declined,
InvalidItems,
EmailPending,
EmailCanceled,
OnHold
}
[JsonProperty(Required = Required.Always)]
internal string tradeofferid { get; set; }
[JsonProperty(Required = Required.Always)]
internal int accountid_other { get; set; }
[JsonProperty(Required = Required.Always)]
internal ETradeOfferState trade_offer_state { get; set; }
[JsonProperty(Required = Required.Always)]
internal List<Steam.Item> items_to_give { get; } = new List<Steam.Item>();
[JsonProperty(Required = Required.Always)]
internal List<Steam.Item> items_to_receive { get; } = new List<Steam.Item>();
// Extra
private ulong _OtherSteamID64 = 0;
internal ulong OtherSteamID64 {
get {
if (_OtherSteamID64 == 0 && accountid_other != 0) {
_OtherSteamID64 = new SteamID((uint) accountid_other, EUniverse.Public, EAccountType.Individual).ConvertToUInt64();
}
return _OtherSteamID64;
}
}
}
internal sealed class TradeOfferRequest {
[JsonProperty(Required = Required.Always)]
internal bool newversion { get; } = true;
[JsonProperty(Required = Required.Always)]
internal int version { get; } = 2;
[JsonProperty(Required = Required.Always)]
internal Steam.ItemList me { get; } = new Steam.ItemList();
[JsonProperty(Required = Required.Always)]
internal Steam.ItemList them { get; } = new Steam.ItemList();
}
}
}

View File

@@ -31,17 +31,18 @@ namespace ArchiSteamFarm {
internal static class Logging { internal static class Logging {
private static readonly object FileLock = new object(); private static readonly object FileLock = new object();
internal static bool LogToFile { get; set; } = false; internal static bool? LogToFile { get; set; } = null;
internal static void Init() { internal static void Init() {
if (!LogToFile.HasValue) {
LogToFile = true;
}
lock (FileLock) { lock (FileLock) {
try { try {
File.Delete(Program.LogFile); File.Delete(Program.LogFile);
} catch (Exception e) { } catch (Exception e) {
bool logToFile = LogToFile;
LogToFile = false;
LogGenericException(e); LogGenericException(e);
LogToFile = logToFile;
} }
} }
} }
@@ -121,15 +122,14 @@ namespace ArchiSteamFarm {
Console.Write(loggedMessage); Console.Write(loggedMessage);
} }
if (LogToFile) { if (LogToFile.GetValueOrDefault()) {
lock (FileLock) { lock (FileLock) {
try { try {
File.AppendAllText(Program.LogFile, loggedMessage); File.AppendAllText(Program.LogFile, loggedMessage);
} catch (Exception e) { } catch (Exception e) {
bool logToFile = LogToFile;
LogToFile = false; LogToFile = false;
LogGenericException(e); LogGenericException(e);
LogToFile = logToFile; LogToFile = true;
} }
} }
} }

View File

@@ -22,9 +22,12 @@
*/ */
using Newtonsoft.Json.Linq; using Newtonsoft.Json;
using System; using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq;
using System.Reflection; using System.Reflection;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -32,6 +35,8 @@ using System.Threading.Tasks;
namespace ArchiSteamFarm { namespace ArchiSteamFarm {
internal static class Program { internal static class Program {
internal enum EUserInputType : byte { internal enum EUserInputType : byte {
Unknown,
DeviceID,
Login, Login,
Password, Password,
PhoneNumber, PhoneNumber,
@@ -48,48 +53,202 @@ namespace ArchiSteamFarm {
Server // Normal + WCF server Server // Normal + WCF server
} }
private const string LatestGithubReleaseURL = "https://api.github.com/repos/JustArchi/ArchiSteamFarm/releases/latest"; private const string GithubReleaseURL = "https://api.github.com/repos/JustArchi/ArchiSteamFarm/releases";
internal const string ASF = "ASF";
internal const string ConfigDirectory = "config"; internal const string ConfigDirectory = "config";
internal const string DebugDirectory = "debug";
internal const string LogFile = "log.txt"; internal const string LogFile = "log.txt";
internal const string GlobalConfigFile = ASF + ".json";
internal const string GlobalDatabaseFile = ASF + ".db";
private static readonly object ConsoleLock = new object(); private static readonly object ConsoleLock = new object();
private static readonly SemaphoreSlim SteamSemaphore = new SemaphoreSlim(1); private static readonly SemaphoreSlim SteamSemaphore = new SemaphoreSlim(1);
private static readonly ManualResetEvent ShutdownResetEvent = new ManualResetEvent(false); private static readonly ManualResetEvent ShutdownResetEvent = new ManualResetEvent(false);
private static readonly Assembly Assembly = Assembly.GetExecutingAssembly(); private static readonly Assembly Assembly = Assembly.GetExecutingAssembly();
private static readonly string ExecutableFile = Assembly.Location; private static readonly string ExecutableFile = Assembly.Location;
private static readonly string ExecutableName = Path.GetFileName(ExecutableFile);
private static readonly string ExecutableDirectory = Path.GetDirectoryName(ExecutableFile); private static readonly string ExecutableDirectory = Path.GetDirectoryName(ExecutableFile);
private static readonly WCF WCF = new WCF(); private static readonly WCF WCF = new WCF();
internal static readonly string Version = Assembly.GetName().Version.ToString(); internal static readonly string Version = Assembly.GetName().Version.ToString();
private static EMode Mode; internal static GlobalConfig GlobalConfig { get; private set; }
internal static GlobalDatabase GlobalDatabase { get; private set; }
internal static bool ConsoleIsBusy { get; private set; } = false; internal static bool ConsoleIsBusy { get; private set; } = false;
private static async Task CheckForUpdate() { private static Timer AutoUpdatesTimer;
JObject response = await WebBrowser.UrlGetToJObject(LatestGithubReleaseURL).ConfigureAwait(false); private static EMode Mode = EMode.Normal;
if (response == null) {
internal static async Task CheckForUpdate() {
string oldExeFile = ExecutableFile + ".old";
// We booted successfully so we can now remove old exe file
if (File.Exists(oldExeFile)) {
try {
File.Delete(oldExeFile);
} catch (Exception e) {
Logging.LogGenericException(e);
return;
}
}
if (GlobalConfig.UpdateChannel == GlobalConfig.EUpdateChannel.Unknown) {
return; return;
} }
string remoteVersion = response["tag_name"].ToString(); string releaseURL = GithubReleaseURL;
if (string.IsNullOrEmpty(remoteVersion)) { if (GlobalConfig.UpdateChannel == GlobalConfig.EUpdateChannel.Stable) {
releaseURL += "/latest";
}
string response = null;
Logging.LogGenericInfo("Checking new version...");
for (byte i = 0; i < WebBrowser.MaxRetries && string.IsNullOrEmpty(response); i++) {
response = await WebBrowser.UrlGetToContent(releaseURL).ConfigureAwait(false);
}
if (string.IsNullOrEmpty(response)) {
Logging.LogGenericWarning("Could not check latest version!");
return; return;
} }
string localVersion = Version; GitHub.ReleaseResponse releaseResponse;
if (GlobalConfig.UpdateChannel == GlobalConfig.EUpdateChannel.Stable) {
try {
releaseResponse = JsonConvert.DeserializeObject<GitHub.ReleaseResponse>(response);
} catch (JsonException e) {
Logging.LogGenericException(e);
return;
}
} else {
List<GitHub.ReleaseResponse> releases;
try {
releases = JsonConvert.DeserializeObject<List<GitHub.ReleaseResponse>>(response);
} catch (JsonException e) {
Logging.LogGenericException(e);
return;
}
Logging.LogGenericInfo("Local version: " + localVersion); if (releases == null || releases.Count == 0) {
Logging.LogGenericInfo("Remote version: " + remoteVersion); Logging.LogGenericWarning("Could not check latest version!");
return;
}
int comparisonResult = localVersion.CompareTo(remoteVersion); releaseResponse = releases[0];
if (comparisonResult < 0) { }
if (string.IsNullOrEmpty(releaseResponse.Tag)) {
Logging.LogGenericWarning("Could not check latest version!");
return;
}
Logging.LogGenericInfo("Local version: " + Version + " | Remote version: " + releaseResponse.Tag);
if (string.Compare(Version, releaseResponse.Tag, StringComparison.Ordinal) >= 0) { // If local version is the same or newer than remote version
if (GlobalConfig.AutoUpdates && AutoUpdatesTimer == null) {
Logging.LogGenericInfo("ASF will automatically check for new versions every 24 hours");
AutoUpdatesTimer = new Timer(
async e => await CheckForUpdate().ConfigureAwait(false),
null,
TimeSpan.FromDays(1), // Delay
TimeSpan.FromDays(1) // Period
);
}
return;
}
if (!GlobalConfig.AutoUpdates) {
Logging.LogGenericInfo("New version is available!"); Logging.LogGenericInfo("New version is available!");
Logging.LogGenericInfo("Consider updating yourself!"); Logging.LogGenericInfo("Consider updating yourself!");
await Utilities.SleepAsync(5000).ConfigureAwait(false); await Utilities.SleepAsync(5000).ConfigureAwait(false);
} else if (comparisonResult > 0) { return;
Logging.LogGenericInfo("You're currently using pre-release version!"); }
Logging.LogGenericInfo("Be careful!");
// Auto update logic starts here
if (releaseResponse.Assets == null) {
Logging.LogGenericWarning("Could not proceed with update because that version doesn't include assets!");
return;
}
GitHub.Asset binaryAsset = null;
foreach (var asset in releaseResponse.Assets) {
if (string.IsNullOrEmpty(asset.Name) || !asset.Name.Equals(ExecutableName)) {
continue;
}
binaryAsset = asset;
break;
}
if (binaryAsset == null) {
Logging.LogGenericWarning("Could not proceed with update because there is no asset that relates to currently running binary!");
return;
}
if (string.IsNullOrEmpty(binaryAsset.DownloadURL)) {
Logging.LogGenericWarning("Could not proceed with update because download URL is empty!");
return;
}
Logging.LogGenericInfo("Downloading new version...");
Stream newExe = await WebBrowser.UrlGetToStream(binaryAsset.DownloadURL).ConfigureAwait(false);
if (newExe == null) {
Logging.LogGenericWarning("Could not download new version!");
return;
}
// We start deep update logic here
string newExeFile = ExecutableFile + ".new";
// Firstly we create new exec
try {
using (FileStream fileStream = File.Open(newExeFile, FileMode.Create)) {
await newExe.CopyToAsync(fileStream).ConfigureAwait(false);
}
} catch (Exception e) {
Logging.LogGenericException(e);
return;
}
// Now we move current -> old
try {
File.Move(ExecutableFile, oldExeFile);
} catch (Exception e) {
Logging.LogGenericException(e);
try {
// Cleanup
File.Delete(newExeFile);
} catch { }
return;
}
// Now we move new -> current
try {
File.Move(newExeFile, ExecutableFile);
} catch (Exception e) {
Logging.LogGenericException(e);
try {
// Cleanup
File.Move(oldExeFile, ExecutableFile);
File.Delete(newExeFile);
} catch { }
return;
}
Logging.LogGenericInfo("Update process is finished! ASF will now restart itself...");
await Utilities.SleepAsync(5000);
if (!Restart()) {
// Make sure that we won't try updating again in this case
if (AutoUpdatesTimer != null) {
AutoUpdatesTimer.Dispose();
AutoUpdatesTimer = null;
}
// Inform user about failure
Logging.LogGenericWarning("ASF could not restart itself, you may need to restart it manually!");
await Utilities.SleepAsync(5000);
} }
} }
@@ -97,24 +256,40 @@ namespace ArchiSteamFarm {
Environment.Exit(exitCode); Environment.Exit(exitCode);
} }
internal static void Restart() { internal static bool Restart() {
System.Diagnostics.Process.Start(ExecutableFile, string.Join(" ", Environment.GetCommandLineArgs())); try {
Exit(); if (Process.Start(ExecutableFile, string.Join(" ", Environment.GetCommandLineArgs().Skip(1))) != null) {
Exit();
return true;
} else {
return false;
}
} catch (Exception e) {
Logging.LogGenericException(e);
return false;
}
} }
internal static async Task LimitSteamRequestsAsync() { internal static async Task LimitSteamRequestsAsync() {
await SteamSemaphore.WaitAsync().ConfigureAwait(false); await SteamSemaphore.WaitAsync().ConfigureAwait(false);
var releaseLater = Task.Run(async () => { Task.Run(async () => {
await Utilities.SleepAsync(7000).ConfigureAwait(false); // We must add some delay to not get caught by Steam rate limiter await Utilities.SleepAsync(GlobalConfig.LoginLimiterDelay * 1000).ConfigureAwait(false);
SteamSemaphore.Release(); SteamSemaphore.Release();
}); }).Forget();
} }
internal static string GetUserInput(string botLogin, EUserInputType userInputType, string extraInformation = null) { internal static string GetUserInput(string botLogin, EUserInputType userInputType, string extraInformation = null) {
if (userInputType == EUserInputType.Unknown) {
return null;
}
string result; string result;
lock (ConsoleLock) { lock (ConsoleLock) {
ConsoleIsBusy = true; ConsoleIsBusy = true;
switch (userInputType) { switch (userInputType) {
case EUserInputType.DeviceID:
Console.Write("<" + botLogin + "> Please enter your Device ID (including \"android:\"): ");
break;
case EUserInputType.Login: case EUserInputType.Login:
Console.Write("<" + botLogin + "> Please enter your login: "); Console.Write("<" + botLogin + "> Please enter your login: ");
break; break;
@@ -141,13 +316,16 @@ namespace ArchiSteamFarm {
case EUserInputType.TwoFactorAuthentication: case EUserInputType.TwoFactorAuthentication:
Console.Write("<" + botLogin + "> Please enter your 2 factor auth code from your authenticator app: "); Console.Write("<" + botLogin + "> Please enter your 2 factor auth code from your authenticator app: ");
break; break;
default:
Console.Write("<" + botLogin + "> Please enter not documented yet value of \"" + userInputType + "\": ");
break;
} }
result = Console.ReadLine(); result = Console.ReadLine();
Console.Clear(); // For security purposes Console.Clear(); // For security purposes
ConsoleIsBusy = false; ConsoleIsBusy = false;
} }
return result.Trim(); // Get rid of all whitespace characters return string.IsNullOrEmpty(result) ? null : result.Trim();
} }
internal static void OnBotShutdown() { internal static void OnBotShutdown() {
@@ -166,8 +344,23 @@ namespace ArchiSteamFarm {
} }
private static void InitServices() { private static void InitServices() {
Logging.Init(); GlobalConfig = GlobalConfig.Load();
if (GlobalConfig == null) {
Logging.LogGenericError("Global config could not be loaded, please make sure that ASF.json exists and is valid!");
Thread.Sleep(5000);
Exit(1);
}
GlobalDatabase = GlobalDatabase.Load();
if (GlobalDatabase == null) {
Logging.LogGenericError("Global database could not be loaded!");
Thread.Sleep(5000);
Exit(1);
}
ArchiWebHandler.Init();
WebBrowser.Init(); WebBrowser.Init();
WCF.Init();
} }
private static void ParseArgs(string[] args) { private static void ParseArgs(string[] args) {
@@ -244,11 +437,7 @@ namespace ArchiSteamFarm {
} }
} }
// By default we're operating on normal mode // Parse args
Mode = EMode.Normal;
Logging.LogToFile = true;
// But that can be overriden by arguments
ParseArgs(args); ParseArgs(args);
// If we ran ASF as a client, we're done by now // If we ran ASF as a client, we're done by now
@@ -256,7 +445,8 @@ namespace ArchiSteamFarm {
return; return;
} }
Task.Run(async () => await CheckForUpdate().ConfigureAwait(false)).Wait(); // From now on it's server mode
Logging.Init();
if (!Directory.Exists(ConfigDirectory)) { if (!Directory.Exists(ConfigDirectory)) {
Logging.LogGenericError("Config directory doesn't exist!"); Logging.LogGenericError("Config directory doesn't exist!");
@@ -264,13 +454,19 @@ namespace ArchiSteamFarm {
Exit(1); Exit(1);
} }
CheckForUpdate().Wait();
// Before attempting to connect, initialize our list of CMs // Before attempting to connect, initialize our list of CMs
Bot.RefreshCMs().Wait(); Bot.RefreshCMs(GlobalDatabase.CellID).Wait();
foreach (var configFile in Directory.EnumerateFiles(ConfigDirectory, "*.json")) { foreach (var configFile in Directory.EnumerateFiles(ConfigDirectory, "*.json")) {
string botName = Path.GetFileNameWithoutExtension(configFile); string botName = Path.GetFileNameWithoutExtension(configFile);
if (botName.Equals(ASF)) {
continue;
}
Bot bot = new Bot(botName); Bot bot = new Bot(botName);
if (!bot.BotConfig.Enabled) { if (bot.BotConfig == null || !bot.BotConfig.Enabled) {
Logging.LogGenericInfo("Not starting this instance because it's disabled in config file", botName); Logging.LogGenericInfo("Not starting this instance because it's disabled in config file", botName);
} }
} }
@@ -280,7 +476,7 @@ namespace ArchiSteamFarm {
string botName = Path.GetFileNameWithoutExtension(configFile); string botName = Path.GetFileNameWithoutExtension(configFile);
Logging.LogGenericWarning("Found legacy " + botName + ".xml config file, it will now be converted to new ASF V2.0 format!"); Logging.LogGenericWarning("Found legacy " + botName + ".xml config file, it will now be converted to new ASF V2.0 format!");
Bot bot = new Bot(botName); Bot bot = new Bot(botName);
if (!bot.BotConfig.Enabled) { if (bot.BotConfig == null || !bot.BotConfig.Enabled) {
Logging.LogGenericInfo("Not starting this instance because it's disabled in config file", botName); Logging.LogGenericInfo("Not starting this instance because it's disabled in config file", botName);
} }
} }

View File

@@ -10,7 +10,7 @@ using System.Runtime.InteropServices;
[assembly: AssemblyConfiguration("")] [assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")] [assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("ArchiSteamFarm")] [assembly: AssemblyProduct("ArchiSteamFarm")]
[assembly: AssemblyCopyright("Copyright © Łukasz Domeradzki 2015")] [assembly: AssemblyCopyright("Copyright © Łukasz Domeradzki 2015-2016")]
[assembly: AssemblyTrademark("")] [assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")] [assembly: AssemblyCulture("")]
@@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers // You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below: // by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("2.0.0.0")] [assembly: AssemblyVersion("2.0.0.8")]
[assembly: AssemblyFileVersion("2.0.0.0")] [assembly: AssemblyFileVersion("2.0.0.8")]

View File

@@ -1,74 +0,0 @@
/*
_ _ _ ____ _ _____
/ \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
/ _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
Copyright 2015-2016 Łukasz "JustArchi" Domeradzki
Contact: JustArchi@JustArchi.net
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
using Newtonsoft.Json;
using SteamKit2;
using System.Collections.Generic;
namespace ArchiSteamFarm {
internal sealed class SteamTradeOffer {
// REF: https://developer.valvesoftware.com/wiki/Steam_Web_API/IEconService#CEcon_TradeOffer
internal enum ETradeOfferState : byte {
Unknown,
Invalid,
Active,
Accepted,
Countered,
Expired,
Canceled,
Declined,
InvalidItems,
EmailPending,
EmailCanceled,
OnHold
}
[JsonProperty(Required = Required.Always)]
internal string tradeofferid { get; set; }
[JsonProperty(Required = Required.Always)]
internal int accountid_other { get; set; }
[JsonProperty(Required = Required.Always)]
internal ETradeOfferState trade_offer_state { get; set; }
[JsonProperty(Required = Required.Always)]
internal List<SteamItem> items_to_give { get; } = new List<SteamItem>();
[JsonProperty(Required = Required.Always)]
internal List<SteamItem> items_to_receive { get; } = new List<SteamItem>();
// Extra
private ulong _OtherSteamID64 = 0;
internal ulong OtherSteamID64 {
get {
if (_OtherSteamID64 == 0 && accountid_other != 0) {
_OtherSteamID64 = new SteamID((uint) accountid_other, EUniverse.Public, EAccountType.Individual).ConvertToUInt64();
}
return _OtherSteamID64;
}
}
}
}

View File

@@ -1,41 +0,0 @@
/*
_ _ _ ____ _ _____
/ \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
/ _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
Copyright 2015-2016 Łukasz "JustArchi" Domeradzki
Contact: JustArchi@JustArchi.net
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
using Newtonsoft.Json;
namespace ArchiSteamFarm {
internal sealed class SteamTradeOfferRequest {
[JsonProperty(Required = Required.Always)]
internal bool newversion { get; } = true;
[JsonProperty(Required = Required.Always)]
internal int version { get; } = 2;
[JsonProperty(Required = Required.Always)]
internal SteamItemList me { get; } = new SteamItemList();
[JsonProperty(Required = Required.Always)]
internal SteamItemList them { get; } = new SteamItemList();
}
}

View File

@@ -39,37 +39,43 @@ namespace ArchiSteamFarm {
internal static async Task LimitInventoryRequestsAsync() { internal static async Task LimitInventoryRequestsAsync() {
await InventorySemaphore.WaitAsync().ConfigureAwait(false); await InventorySemaphore.WaitAsync().ConfigureAwait(false);
var releaseLater = Task.Run(async () => { Task.Run(async () => {
await Utilities.SleepAsync(3000).ConfigureAwait(false); // We must add some delay to not get caught by Steam rate limiter await Utilities.SleepAsync(Program.GlobalConfig.InventoryLimiterDelay * 1000).ConfigureAwait(false);
InventorySemaphore.Release(); InventorySemaphore.Release();
}); }).Forget();
} }
internal Trading(Bot bot) { internal Trading(Bot bot) {
if (bot == null) {
return;
}
Bot = bot; Bot = bot;
} }
internal async void CheckTrades() { internal async void CheckTrades() {
if (ParsingTasks < 2) { if (ParsingTasks >= 2) {
ParsingTasks++; return;
await Semaphore.WaitAsync().ConfigureAwait(false);
await ParseActiveTrades().ConfigureAwait(false);
Semaphore.Release();
ParsingTasks--;
} }
ParsingTasks++;
await Semaphore.WaitAsync().ConfigureAwait(false);
await ParseActiveTrades().ConfigureAwait(false);
Semaphore.Release();
ParsingTasks--;
} }
private async Task ParseActiveTrades() { private async Task ParseActiveTrades() {
List<SteamTradeOffer> tradeOffers = Bot.ArchiWebHandler.GetTradeOffers(); List<Steam.TradeOffer> tradeOffers = Bot.ArchiWebHandler.GetTradeOffers();
if (tradeOffers == null) { if (tradeOffers == null) {
return; return;
} }
List<Task> tasks = new List<Task>(); List<Task> tasks = new List<Task>();
foreach (SteamTradeOffer tradeOffer in tradeOffers) { foreach (Steam.TradeOffer tradeOffer in tradeOffers) {
if (tradeOffer.trade_offer_state == SteamTradeOffer.ETradeOfferState.Active) { if (tradeOffer.trade_offer_state == Steam.TradeOffer.ETradeOfferState.Active) {
tasks.Add(Task.Run(async () => await ParseTrade(tradeOffer).ConfigureAwait(false))); tasks.Add(Task.Run(async () => await ParseTrade(tradeOffer).ConfigureAwait(false)));
} }
} }
@@ -78,7 +84,7 @@ namespace ArchiSteamFarm {
await Bot.AcceptAllConfirmations().ConfigureAwait(false); await Bot.AcceptAllConfirmations().ConfigureAwait(false);
} }
private async Task ParseTrade(SteamTradeOffer tradeOffer) { private async Task ParseTrade(Steam.TradeOffer tradeOffer) {
if (tradeOffer == null) { if (tradeOffer == null) {
return; return;
} }

View File

@@ -27,6 +27,8 @@ using System.Threading.Tasks;
namespace ArchiSteamFarm { namespace ArchiSteamFarm {
internal static class Utilities { internal static class Utilities {
internal static void Forget(this Task task) { }
internal static async Task SleepAsync(int miliseconds) { internal static async Task SleepAsync(int miliseconds) {
await Task.Delay(miliseconds).ConfigureAwait(false); await Task.Delay(miliseconds).ConfigureAwait(false);
} }

View File

@@ -23,6 +23,7 @@
*/ */
using System; using System;
using System.Linq;
using System.ServiceModel; using System.ServiceModel;
using System.ServiceModel.Channels; using System.ServiceModel.Channels;
@@ -35,11 +36,15 @@ namespace ArchiSteamFarm {
internal sealed class WCF : IWCF { internal sealed class WCF : IWCF {
private const string URL = "http://localhost:1242/ASF"; // 1242 = 1024 + A(65) + S(83) + F(70) private static string URL = "http://localhost:1242/ASF";
private ServiceHost ServiceHost; private ServiceHost ServiceHost;
private Client Client; private Client Client;
internal static void Init() {
URL = "http://" + Program.GlobalConfig.WCFHostname + ":" + Program.GlobalConfig.WCFPort + "/ASF";
}
internal bool IsServerRunning() { internal bool IsServerRunning() {
return ServiceHost != null; return ServiceHost != null;
} }
@@ -96,7 +101,7 @@ namespace ArchiSteamFarm {
if (args.Length > 1) { // If we have args[1] provided, use given botName if (args.Length > 1) { // If we have args[1] provided, use given botName
botName = args[1]; botName = args[1];
} else { // If not, just pick first one } else { // If not, just pick first one
botName = Bot.GetAnyBotName(); botName = Bot.Bots.Keys.FirstOrDefault();
} }
if (string.IsNullOrEmpty(botName)) { if (string.IsNullOrEmpty(botName)) {

View File

@@ -26,6 +26,7 @@ using HtmlAgilityPack;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Net; using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Text; using System.Text;
@@ -33,7 +34,6 @@ using System.Threading.Tasks;
namespace ArchiSteamFarm { namespace ArchiSteamFarm {
internal static class WebBrowser { internal static class WebBrowser {
internal const byte HttpTimeout = 180; // In seconds, how long we can wait for server's response
internal const byte MaxConnections = 10; // Defines maximum number of connections per ServicePoint. Be careful, as it also defines maximum number of sockets in CLOSE_WAIT state internal const byte MaxConnections = 10; // Defines maximum number of connections per ServicePoint. Be careful, as it also defines maximum number of sockets in CLOSE_WAIT state
internal const byte MaxIdleTime = 15; // In seconds, how long socket is allowed to stay in CLOSE_WAIT state after there are no connections to it internal const byte MaxIdleTime = 15; // In seconds, how long socket is allowed to stay in CLOSE_WAIT state after there are no connections to it
internal const byte MaxRetries = 5; // Defines maximum number of retries, UrlRequest() does not handle retry by itself (it's app responsibility) internal const byte MaxRetries = 5; // Defines maximum number of retries, UrlRequest() does not handle retry by itself (it's app responsibility)
@@ -42,10 +42,12 @@ namespace ArchiSteamFarm {
private static readonly HttpClient HttpClient = new HttpClient(new HttpClientHandler { private static readonly HttpClient HttpClient = new HttpClient(new HttpClientHandler {
UseCookies = false UseCookies = false
}) { }) {
Timeout = TimeSpan.FromSeconds(HttpTimeout) Timeout = TimeSpan.FromSeconds(30)
}; };
internal static void Init() { internal static void Init() {
HttpClient.Timeout = TimeSpan.FromSeconds(Program.GlobalConfig.HttpTimeout);
// Most web services expect that UserAgent is set, so we declare it globally // Most web services expect that UserAgent is set, so we declare it globally
// Any request can override that on as-needed basis (see: RequestOptions.FakeUserAgent) // Any request can override that on as-needed basis (see: RequestOptions.FakeUserAgent)
HttpClient.DefaultRequestHeaders.UserAgent.ParseAdd(DefaultUserAgent); HttpClient.DefaultRequestHeaders.UserAgent.ParseAdd(DefaultUserAgent);
@@ -80,7 +82,7 @@ namespace ArchiSteamFarm {
return await UrlRequest(request, HttpMethod.Post, data, cookies, referer).ConfigureAwait(false); return await UrlRequest(request, HttpMethod.Post, data, cookies, referer).ConfigureAwait(false);
} }
internal static async Task<string> UrlGetToContent(string request, Dictionary<string, string> cookies, string referer = null) { internal static async Task<string> UrlGetToContent(string request, Dictionary<string, string> cookies = null, string referer = null) {
if (string.IsNullOrEmpty(request)) { if (string.IsNullOrEmpty(request)) {
return null; return null;
} }
@@ -90,12 +92,28 @@ namespace ArchiSteamFarm {
return null; return null;
} }
HttpContent httpContent = httpResponse.Content; if (httpResponse.Content == null) {
if (httpContent == null) {
return null; return null;
} }
return await httpContent.ReadAsStringAsync().ConfigureAwait(false); return await httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false);
}
internal static async Task<Stream> UrlGetToStream(string request, Dictionary<string, string> cookies = null, string referer = null) {
if (string.IsNullOrEmpty(request)) {
return null;
}
HttpResponseMessage httpResponse = await UrlGet(request, cookies, referer).ConfigureAwait(false);
if (httpResponse == null) {
return null;
}
if (httpResponse.Content == null) {
return null;
}
return await httpResponse.Content.ReadAsStreamAsync().ConfigureAwait(false);
} }
internal static async Task<HtmlDocument> UrlGetToHtmlDocument(string request, Dictionary<string, string> cookies = null, string referer = null) { internal static async Task<HtmlDocument> UrlGetToHtmlDocument(string request, Dictionary<string, string> cookies = null, string referer = null) {
@@ -144,7 +162,7 @@ namespace ArchiSteamFarm {
HttpResponseMessage responseMessage; HttpResponseMessage responseMessage;
using (HttpRequestMessage requestMessage = new HttpRequestMessage(httpMethod, request)) { using (HttpRequestMessage requestMessage = new HttpRequestMessage(httpMethod, request)) {
if (data != null) { if (data != null && data.Count > 0) {
try { try {
requestMessage.Content = new FormUrlEncodedContent(data); requestMessage.Content = new FormUrlEncodedContent(data);
} catch (UriFormatException e) { } catch (UriFormatException e) {
@@ -161,7 +179,7 @@ namespace ArchiSteamFarm {
requestMessage.Headers.Add("Cookie", cookieHeader.ToString()); requestMessage.Headers.Add("Cookie", cookieHeader.ToString());
} }
if (referer != null) { if (!string.IsNullOrEmpty(referer)) {
requestMessage.Headers.Referrer = new Uri(referer); requestMessage.Headers.Referrer = new Uri(referer);
} }

View File

@@ -0,0 +1,22 @@
{
"Debug": false,
"AutoUpdates": true,
"UpdateChannel": 1,
"MaxFarmingTime": 10,
"IdleFarmingPeriod": 3,
"FarmingDelay": 5,
"AccountPlayingDelay": 5,
"LoginLimiterDelay": 7,
"InventoryLimiterDelay": 3,
"HttpTimeout": 30,
"WCFHostname": "localhost",
"WCFPort": 1242,
"Statistics": true,
"Blacklist": [
267420,
303700,
335590,
368020,
425280
]
}

View File

@@ -18,8 +18,9 @@
"SendOnFarmingFinished": false, "SendOnFarmingFinished": false,
"SteamTradeToken": null, "SteamTradeToken": null,
"SendTradePeriod": 0, "SendTradePeriod": 0,
"AcceptConfirmationsPeriod": 0,
"CustomGamePlayedWhileIdle": null,
"GamesPlayedWhileIdle": [ "GamesPlayedWhileIdle": [
0 0
], ]
"Statistics": true
} }

View File

@@ -8,8 +8,29 @@ namespace SteamAuth
{ {
public class Confirmation public class Confirmation
{ {
public string ConfirmationID; public string ID;
public string ConfirmationKey; public string Key;
public string ConfirmationDescription; public string Description;
public ConfirmationType ConfType
{
get
{
if (String.IsNullOrEmpty(Description)) return ConfirmationType.Unknown;
if (Description.StartsWith("Confirm ")) return ConfirmationType.GenericConfirmation;
if (Description.StartsWith("Trade with ")) return ConfirmationType.Trade;
if (Description.StartsWith("Sell -")) return ConfirmationType.MarketSellTransaction;
return ConfirmationType.Unknown;
}
}
public enum ConfirmationType
{
GenericConfirmation,
Trade,
MarketSellTransaction,
Unknown
}
} }
} }

View File

@@ -71,7 +71,7 @@ namespace SteamAuth
if (removeResponse == null || removeResponse.Response == null || !removeResponse.Response.Success) return false; if (removeResponse == null || removeResponse.Response == null || !removeResponse.Response.Success) return false;
return true; return true;
} }
catch (Exception e) catch (Exception)
{ {
return false; return false;
} }
@@ -115,7 +115,7 @@ namespace SteamAuth
codePoint /= steamGuardCodeTranslations.Length; codePoint /= steamGuardCodeTranslations.Length;
} }
} }
catch (Exception e) catch (Exception)
{ {
return null; //Change later, catch-alls are bad! return null; //Change later, catch-alls are bad!
} }
@@ -162,9 +162,9 @@ namespace SteamAuth
string confDesc = confDescs[i].Groups[1].Value; string confDesc = confDescs[i].Groups[1].Value;
Confirmation conf = new Confirmation() Confirmation conf = new Confirmation()
{ {
ConfirmationDescription = confDesc, Description = confDesc,
ConfirmationID = confID, ID = confID,
ConfirmationKey = confKey Key = confKey
}; };
ret.Add(conf); ret.Add(conf);
} }
@@ -212,9 +212,9 @@ namespace SteamAuth
string confDesc = confDescs[i].Groups[1].Value; string confDesc = confDescs[i].Groups[1].Value;
Confirmation conf = new Confirmation() Confirmation conf = new Confirmation()
{ {
ConfirmationDescription = confDesc, Description = confDesc,
ConfirmationID = confID, ID = confID,
ConfirmationKey = confKey Key = confKey
}; };
ret.Add(conf); ret.Add(conf);
} }
@@ -268,7 +268,7 @@ namespace SteamAuth
this.Session.SteamLoginSecure = tokenSecure; this.Session.SteamLoginSecure = tokenSecure;
return true; return true;
} }
catch (Exception e) catch (Exception)
{ {
return false; return false;
} }
@@ -300,7 +300,7 @@ namespace SteamAuth
this.Session.SteamLoginSecure = tokenSecure; this.Session.SteamLoginSecure = tokenSecure;
return true; return true;
} }
catch (Exception e) catch (Exception)
{ {
return false; return false;
} }
@@ -308,7 +308,7 @@ namespace SteamAuth
private ConfirmationDetailsResponse _getConfirmationDetails(Confirmation conf) private ConfirmationDetailsResponse _getConfirmationDetails(Confirmation conf)
{ {
string url = APIEndpoints.COMMUNITY_BASE + "/mobileconf/details/" + conf.ConfirmationID + "?"; string url = APIEndpoints.COMMUNITY_BASE + "/mobileconf/details/" + conf.ID + "?";
string queryString = GenerateConfirmationQueryParams("details"); string queryString = GenerateConfirmationQueryParams("details");
url += queryString; url += queryString;
@@ -329,7 +329,7 @@ namespace SteamAuth
string url = APIEndpoints.COMMUNITY_BASE + "/mobileconf/ajaxop"; string url = APIEndpoints.COMMUNITY_BASE + "/mobileconf/ajaxop";
string queryString = "?op=" + op + "&"; string queryString = "?op=" + op + "&";
queryString += GenerateConfirmationQueryParams(op); queryString += GenerateConfirmationQueryParams(op);
queryString += "&cid=" + conf.ConfirmationID + "&ck=" + conf.ConfirmationKey; queryString += "&cid=" + conf.ID + "&ck=" + conf.Key;
url += queryString; url += queryString;
CookieContainer cookies = new CookieContainer(); CookieContainer cookies = new CookieContainer();
@@ -401,7 +401,7 @@ namespace SteamAuth
string hash = WebUtility.UrlEncode(encodedData); string hash = WebUtility.UrlEncode(encodedData);
return hash; return hash;
} }
catch (Exception e) catch (Exception)
{ {
return null; //Fix soon: catch-all is BAD! return null; //Fix soon: catch-all is BAD!
} }

View File

@@ -161,8 +161,6 @@ namespace SteamAuth
this.LoggedIn = true; this.LoggedIn = true;
return LoginResult.LoginOkay; return LoginResult.LoginOkay;
} }
return LoginResult.GeneralFailure;
} }
private class LoginResponse private class LoginResponse

BIN
tools/NetHook2.dll Normal file

Binary file not shown.

BIN
tools/NetHookAnalyzer2.exe Normal file

Binary file not shown.

1
tools/hook.bat Normal file
View File

@@ -0,0 +1 @@
rundll32 NetHook2.dll,Inject

1
tools/unhook.bat Normal file
View File

@@ -0,0 +1 @@
rundll32 NetHook2.dll,Eject