Compare commits

..

45 Commits

Author SHA1 Message Date
JustArchi
54ad58a22d Misc 2016-04-12 17:09:57 +02:00
JustArchi
790e6baf46 ASF-specific WebBrowser enhancements, closes #192 2016-04-12 16:58:45 +02:00
JustArchi
c8fb715558 Fix for new code 2016-04-12 16:37:49 +02:00
JustArchi
2ab5e6013d Misc 2016-04-12 16:36:09 +02:00
JustArchi
3e0c34e62c Misc 2016-04-12 07:44:59 +02:00
JustArchi
65b31e5537 Bring in latest ArchiBoT WebBrowser
Highlights include: Support for automatic decompression via GZip/Deflate, more aggressive disposal of response messages, rewrite of cookie handling from dictionary to cookiecontainer, debug log of also timed out messages and more.
We sacrifice the performance and scalability of single HttpClient doing the work for being less error prone on eventual steam fuckups (CookieContainer can adapt to always changing structure)
2016-04-12 07:40:02 +02:00
JustArchi
62a6e38e47 Misc 2016-04-10 18:25:22 +02:00
JustArchi
0b50a45336 Checking update does not depend on old binary removal error 2016-04-09 22:15:04 +02:00
JustArchi
8d1d162b02 Misc 2016-04-09 22:11:11 +02:00
JustArchi
dbe13a1965 Bump 2016-04-09 00:06:39 +02:00
JustArchi
fc13633f5e Misc 2016-04-08 17:33:00 +02:00
JustArchi
d0cc10f3c6 Always provide 2FA code when ASF 2FA is enabled 2016-04-08 17:12:08 +02:00
JustArchi
567931a4cc Misc 2016-04-08 04:30:51 +02:00
JustArchi
396dc17ab2 Bump 2016-04-08 04:22:41 +02:00
JustArchi
288cc29338 Be consistent 2016-04-08 03:58:46 +02:00
JustArchi
844ca7da94 Enhance !owns command
Now doesn't only support multiple games for querying, but also a mixed combination of appIDs and strings
2016-04-08 03:57:03 +02:00
JustArchi
b14b9f87c7 Handle really long messages properly 2016-04-07 03:14:49 +02:00
JustArchi
8aa086cc27 Fix rare crash 2016-04-07 01:45:08 +02:00
JustArchi
cf00989d84 Misc 2016-04-07 01:39:02 +02:00
JustArchi
8f2f85282c Bump 2016-04-06 17:05:49 +02:00
JustArchi
fa12ffd9d0 Add headlness mode 2016-04-06 16:37:45 +02:00
JustArchi
ecb27adedd Bump 2016-04-06 15:37:55 +02:00
JustArchi
6e9be09944 Misc 2016-04-06 15:30:03 +02:00
JustArchi
6a79a89a10 Allow offline bot to handle keys redeeming as well 2016-04-06 15:28:28 +02:00
JustArchi
d67be4f092 Fix ResponseRedeem() and make it possible to redeem multiple keys 2016-04-06 15:25:52 +02:00
JustArchi
a8e1039e32 Add !2fano 2016-04-05 03:41:59 +02:00
JustArchi
2ad9d9e197 Bump 2016-04-03 17:00:40 +02:00
JustArchi
fd6e2c72d7 Major cleanup & code review of ResponseRedeem() 2016-04-02 19:23:09 +02:00
JustArchi
1eed0f7647 Misc 2016-04-02 13:41:53 +02:00
JustArchi
c018c08260 Misc WCF enhancements 2016-04-02 13:41:08 +02:00
JustArchi
72fa98cb89 Improve exit/restart 2016-04-02 13:08:43 +02:00
JustArchi
71215d695e Bump 2016-04-02 12:33:41 +02:00
JustArchi
75b785d4b6 Misc 2016-04-01 20:19:17 +02:00
JustArchi
12e32692cb Code review 2016-04-01 20:18:21 +02:00
JustArchi
6158a9268d Misc 2016-04-01 17:14:03 +02:00
JustArchi
a48e53585a Pretty sure those are no longer needed 2016-03-30 13:10:00 +02:00
JustArchi
33383633ea Fix ultra rare crash 2016-03-30 01:41:53 +02:00
JustArchi
3f7359c608 Misc 2016-03-29 23:32:12 +02:00
JustArchi
d8b59c6889 Add Linux scripts 2016-03-29 22:58:18 +02:00
JustArchi
0f71b788cb Use /my/ steamcommunity trick 2016-03-29 14:48:31 +02:00
JustArchi
d012255a21 EXPERIMENTAL: Steam session improvements
1. Make sure that every call to steamcommunity has active session
2. Move whole userspace logic for session handling to ArchiWebHandler (and Bot)
3. Implement session caching and TTL so we won't send IsLoggedIn() on each ArchiWebHandler call
4. Instead of restarting whole steam account, just refresh the session via ArchiWebHandler instead
2016-03-29 14:33:05 +02:00
JustArchi
8fc39a44cd Misc 2016-03-28 15:34:59 +02:00
JustArchi
c6fe424fcc Refuse to handle https requests when ForceHttp is true 2016-03-28 15:34:10 +02:00
JustArchi
12488dafd3 Misc 2016-03-28 13:11:02 +02:00
JustArchi
3b53491567 Bump 2016-03-28 00:18:07 +02:00
18 changed files with 770 additions and 472 deletions

View File

@@ -32,6 +32,16 @@ using System.Threading.Tasks;
namespace ArchiSteamFarm { namespace ArchiSteamFarm {
internal sealed class ArchiHandler : ClientMsgHandler { internal sealed class ArchiHandler : ClientMsgHandler {
private readonly Bot Bot;
internal ArchiHandler(Bot bot) {
if (bot == null) {
return;
}
Bot = bot;
}
/* /*
____ _ _ _ _ ____ _ _ _ _
/ ___| __ _ | || || |__ __ _ ___ | | __ ___ / ___| __ _ | || || |__ __ _ ___ | | __ ___
@@ -257,10 +267,11 @@ namespace ArchiSteamFarm {
request.Body.key = key; request.Body.key = key;
Client.Send(request); Client.Send(request);
try { try {
return await new AsyncJob<PurchaseResponseCallback>(Client, request.SourceJobID); return await new AsyncJob<PurchaseResponseCallback>(Client, request.SourceJobID);
} catch (Exception e) { } catch (Exception e) {
Logging.LogGenericException(e); Logging.LogGenericException(e, Bot.BotName);
return null; return null;
} }
} }

View File

@@ -32,21 +32,22 @@ using System.Net.Http;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Xml; using System.Xml;
using System.Threading;
namespace ArchiSteamFarm { namespace ArchiSteamFarm {
internal sealed class ArchiWebHandler { internal sealed class ArchiWebHandler {
private const string SteamCommunity = "steamcommunity.com"; private const string SteamCommunity = "steamcommunity.com";
private const byte MinSessionTTL = 15; // Assume session is valid for at least that amount of seconds
private static string SteamCommunityURL = "https://" + SteamCommunity; private static string SteamCommunityURL = "https://" + SteamCommunity;
private static int Timeout = 30 * 1000; private static int Timeout = GlobalConfig.DefaultHttpTimeout * 1000;
private readonly Bot Bot; private readonly Bot Bot;
private readonly Dictionary<string, string> Cookie = new Dictionary<string, string>(4); private readonly SemaphoreSlim SessionSemaphore = new SemaphoreSlim(1);
private readonly WebBrowser WebBrowser;
internal bool IsInitialized { get; private set; } private DateTime LastSessionRefreshCheck = DateTime.MinValue;
private ulong SteamID;
internal static void Init() { internal static void Init() {
Timeout = Program.GlobalConfig.HttpTimeout * 1000; Timeout = Program.GlobalConfig.HttpTimeout * 1000;
@@ -59,20 +60,18 @@ namespace ArchiSteamFarm {
} }
Bot = bot; Bot = bot;
WebBrowser = new WebBrowser(bot.BotName);
} }
internal void OnDisconnected() { internal bool Init(SteamClient steamClient, string webAPIUserNonce, string parentalPin) {
IsInitialized = false; if (steamClient == null || steamClient.SteamID == null || string.IsNullOrEmpty(webAPIUserNonce)) {
}
internal async Task<bool> Init(SteamClient steamClient, string webAPIUserNonce, string parentalPin) {
if (steamClient == null || steamClient.SteamID == null || string.IsNullOrEmpty(webAPIUserNonce) || IsInitialized) {
return false; return false;
} }
SteamID = steamClient.SteamID; ulong steamID = steamClient.SteamID;
string sessionID = Convert.ToBase64String(Encoding.UTF8.GetBytes(SteamID.ToString())); string sessionID = Convert.ToBase64String(Encoding.UTF8.GetBytes(steamID.ToString()));
// Generate an AES session key // Generate an AES session key
byte[] sessionKey = CryptoHelper.GenerateRandomBlock(32); byte[] sessionKey = CryptoHelper.GenerateRandomBlock(32);
@@ -99,7 +98,7 @@ namespace ArchiSteamFarm {
try { try {
authResult = iSteamUserAuth.AuthenticateUser( authResult = iSteamUserAuth.AuthenticateUser(
steamid: SteamID, steamid: steamID,
sessionkey: Encoding.ASCII.GetString(WebUtility.UrlEncodeToBytes(cryptedSessionKey, 0, cryptedSessionKey.Length)), sessionkey: Encoding.ASCII.GetString(WebUtility.UrlEncodeToBytes(cryptedSessionKey, 0, cryptedSessionKey.Length)),
encrypted_loginkey: Encoding.ASCII.GetString(WebUtility.UrlEncodeToBytes(cryptedLoginKey, 0, cryptedLoginKey.Length)), encrypted_loginkey: Encoding.ASCII.GetString(WebUtility.UrlEncodeToBytes(cryptedLoginKey, 0, cryptedLoginKey.Length)),
method: WebRequestMethods.Http.Post, method: WebRequestMethods.Http.Post,
@@ -117,30 +116,26 @@ namespace ArchiSteamFarm {
Logging.LogGenericInfo("Success!", Bot.BotName); Logging.LogGenericInfo("Success!", Bot.BotName);
string steamLogin = authResult["token"].AsString(); WebBrowser.CookieContainer.Add(new Cookie("sessionid", sessionID, "/", "." + SteamCommunity));
string steamLoginSecure = authResult["tokensecure"].AsString();
Cookie["sessionid"] = sessionID; string steamLogin = authResult["token"].Value;
Cookie["steamLogin"] = steamLogin; WebBrowser.CookieContainer.Add(new Cookie("steamLogin", steamLogin, "/", "." + SteamCommunity));
Cookie["steamLoginSecure"] = steamLoginSecure;
// The below is used for display purposes only string steamLoginSecure = authResult["tokensecure"].Value;
Cookie["webTradeEligibility"] = "{\"allowed\":0,\"reason\":0,\"allowed_at_time\":0,\"steamguard_required_days\":0,\"sales_this_year\":0,\"max_sales_per_year\":0,\"forms_requested\":0}"; WebBrowser.CookieContainer.Add(new Cookie("steamLoginSecure", steamLoginSecure, "/", "." + SteamCommunity));
await UnlockParentalAccount(parentalPin).ConfigureAwait(false); if (!UnlockParentalAccount(parentalPin).Result) {
return false;
}
IsInitialized = true; LastSessionRefreshCheck = DateTime.Now;
return true; return true;
} }
internal async Task<bool?> IsLoggedIn() { internal async Task<bool?> IsLoggedIn() {
if (SteamID == 0) {
return false;
}
HtmlDocument htmlDocument = null; HtmlDocument htmlDocument = null;
for (byte i = 0; i < WebBrowser.MaxRetries && htmlDocument == null; i++) { for (byte i = 0; i < WebBrowser.MaxRetries && htmlDocument == null; i++) {
htmlDocument = await WebBrowser.UrlGetToHtmlDocument(SteamCommunityURL + "/my/profile", Cookie).ConfigureAwait(false); htmlDocument = await WebBrowser.UrlGetToHtmlDocument(SteamCommunityURL + "/my/profile").ConfigureAwait(false);
} }
if (htmlDocument == null) { if (htmlDocument == null) {
@@ -152,27 +147,46 @@ namespace ArchiSteamFarm {
return htmlNode != null; return htmlNode != null;
} }
internal async Task<bool> ReconnectIfNeeded() { internal async Task<bool> RefreshSessionIfNeeded() {
bool? isLoggedIn = await IsLoggedIn().ConfigureAwait(false); DateTime now = DateTime.Now;
if (isLoggedIn.HasValue && !isLoggedIn.Value) { if (now.Subtract(LastSessionRefreshCheck).TotalSeconds < MinSessionTTL) {
Logging.LogGenericInfo("Reconnecting because our sessionID expired!", Bot.BotName);
Bot.RestartIfRunning().Forget();
return true; return true;
} }
return false; await SessionSemaphore.WaitAsync().ConfigureAwait(false);
now = DateTime.Now;
if (now.Subtract(LastSessionRefreshCheck).TotalSeconds < MinSessionTTL) {
SessionSemaphore.Release();
return true;
}
bool result;
bool? isLoggedIn = await IsLoggedIn().ConfigureAwait(false);
if (isLoggedIn.GetValueOrDefault(true)) {
result = true;
now = DateTime.Now;
LastSessionRefreshCheck = now;
} else {
Logging.LogGenericInfo("Refreshing our session!", Bot.BotName);
result = await Bot.RefreshSession().ConfigureAwait(false);
}
SessionSemaphore.Release();
return result;
} }
internal async Task<Dictionary<uint, string>> GetOwnedGames() { internal async Task<Dictionary<uint, string>> GetOwnedGames() {
if (SteamID == 0) { if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
return null; return null;
} }
string request = SteamCommunityURL + "/profiles/" + SteamID + "/games/?xml=1"; string request = SteamCommunityURL + "/my/games/?xml=1";
XmlDocument response = null; XmlDocument response = null;
for (byte i = 0; i < WebBrowser.MaxRetries && response == null; i++) { for (byte i = 0; i < WebBrowser.MaxRetries && response == null; i++) {
response = await WebBrowser.UrlGetToXML(request, Cookie).ConfigureAwait(false); response = await WebBrowser.UrlGetToXML(request).ConfigureAwait(false);
} }
if (response == null) { if (response == null) {
@@ -273,8 +287,13 @@ namespace ArchiSteamFarm {
return false; return false;
} }
string sessionID; if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
if (!Cookie.TryGetValue("sessionid", out sessionID)) { return false;
}
string sessionID = WebBrowser.CookieContainer.GetCookieValue(SteamCommunityURL, "sessionid");
if (string.IsNullOrEmpty(sessionID)) {
Logging.LogNullError("sessionID");
return false; return false;
} }
@@ -285,12 +304,12 @@ namespace ArchiSteamFarm {
{"action", "join"} {"action", "join"}
}; };
HttpResponseMessage response = null; bool result = false;
for (byte i = 0; i < WebBrowser.MaxRetries && response == null; i++) { for (byte i = 0; i < WebBrowser.MaxRetries && !result; i++) {
response = await WebBrowser.UrlPost(request, data, Cookie).ConfigureAwait(false); result = await WebBrowser.UrlPost(request, data).ConfigureAwait(false);
} }
if (response == null) { if (!result) {
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName); Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
return false; return false;
} }
@@ -303,8 +322,13 @@ namespace ArchiSteamFarm {
return false; return false;
} }
string sessionID; if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
if (!Cookie.TryGetValue("sessionid", out sessionID)) { return false;
}
string sessionID = WebBrowser.CookieContainer.GetCookieValue(SteamCommunityURL, "sessionid");
if (string.IsNullOrEmpty(sessionID)) {
Logging.LogNullError("sessionID");
return false; return false;
} }
@@ -317,42 +341,12 @@ namespace ArchiSteamFarm {
{"tradeofferid", tradeID.ToString()} {"tradeofferid", tradeID.ToString()}
}; };
HttpResponseMessage response = null; bool result = false;
for (byte i = 0; i < WebBrowser.MaxRetries && response == null; i++) { for (byte i = 0; i < WebBrowser.MaxRetries && !result; i++) {
response = await WebBrowser.UrlPost(request, data, Cookie, referer).ConfigureAwait(false); result = await WebBrowser.UrlPost(request, data, referer).ConfigureAwait(false);
} }
if (response == null) { if (!result) {
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
return false;
}
return true;
}
internal bool DeclineTradeOffer(ulong tradeID) {
if (tradeID == 0 || string.IsNullOrEmpty(Bot.BotConfig.SteamApiKey)) {
return false;
}
KeyValue response = null;
using (dynamic iEconService = WebAPI.GetInterface("IEconService", Bot.BotConfig.SteamApiKey)) {
iEconService.Timeout = Timeout;
for (byte i = 0; i < WebBrowser.MaxRetries && response == null; i++) {
try {
response = iEconService.DeclineTradeOffer(
tradeofferid: tradeID.ToString(),
method: WebRequestMethods.Http.Post,
secure: !Program.GlobalConfig.ForceHttp
);
} catch (Exception e) {
Logging.LogGenericException(e, Bot.BotName);
}
}
}
if (response == null) {
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName); Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
return false; return false;
} }
@@ -361,9 +355,13 @@ namespace ArchiSteamFarm {
} }
internal async Task<List<Steam.Item>> GetMyTradableInventory() { internal async Task<List<Steam.Item>> GetMyTradableInventory() {
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
return null;
}
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(SteamCommunityURL + "/my/inventory/json/753/6?trading=1", Cookie).ConfigureAwait(false); jObject = await WebBrowser.UrlGetToJObject(SteamCommunityURL + "/my/inventory/json/753/6?trading=1").ConfigureAwait(false);
} }
if (jObject == null) { if (jObject == null) {
@@ -394,8 +392,13 @@ namespace ArchiSteamFarm {
return false; return false;
} }
string sessionID; if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
if (!Cookie.TryGetValue("sessionid", out sessionID)) { return false;
}
string sessionID = WebBrowser.CookieContainer.GetCookieValue(SteamCommunityURL, "sessionid");
if (string.IsNullOrEmpty(sessionID)) {
Logging.LogNullError("sessionID");
return false; return false;
} }
@@ -434,12 +437,12 @@ namespace ArchiSteamFarm {
{"trade_offer_create_params", string.IsNullOrEmpty(token) ? "" : $"{{\"trade_offer_access_token\":\"{token}\"}}"} {"trade_offer_create_params", string.IsNullOrEmpty(token) ? "" : $"{{\"trade_offer_access_token\":\"{token}\"}}"}
}; };
HttpResponseMessage response = null; bool result = false;
for (byte i = 0; i < WebBrowser.MaxRetries && response == null; i++) { for (byte i = 0; i < WebBrowser.MaxRetries && !result; i++) {
response = await WebBrowser.UrlPost(request, data, Cookie, referer).ConfigureAwait(false); result = await WebBrowser.UrlPost(request, data, referer).ConfigureAwait(false);
} }
if (response == null) { if (!result) {
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName); Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
return false; return false;
} }
@@ -449,13 +452,17 @@ namespace ArchiSteamFarm {
} }
internal async Task<HtmlDocument> GetBadgePage(byte page) { internal async Task<HtmlDocument> GetBadgePage(byte page) {
if (page == 0 || SteamID == 0) { if (page == 0) {
return null;
}
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
return null; return null;
} }
HtmlDocument htmlDocument = null; HtmlDocument htmlDocument = null;
for (byte i = 0; i < WebBrowser.MaxRetries && htmlDocument == null; i++) { for (byte i = 0; i < WebBrowser.MaxRetries && htmlDocument == null; i++) {
htmlDocument = await WebBrowser.UrlGetToHtmlDocument(SteamCommunityURL + "/profiles/" + SteamID + "/badges?l=english&p=" + page, Cookie).ConfigureAwait(false); htmlDocument = await WebBrowser.UrlGetToHtmlDocument(SteamCommunityURL + "/my/badges?p=" + page).ConfigureAwait(false);
} }
if (htmlDocument == null) { if (htmlDocument == null) {
@@ -467,13 +474,17 @@ namespace ArchiSteamFarm {
} }
internal async Task<HtmlDocument> GetGameCardsPage(ulong appID) { internal async Task<HtmlDocument> GetGameCardsPage(ulong appID) {
if (appID == 0 || SteamID == 0) { if (appID == 0) {
return null;
}
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
return null; return null;
} }
HtmlDocument htmlDocument = null; HtmlDocument htmlDocument = null;
for (byte i = 0; i < WebBrowser.MaxRetries && htmlDocument == null; i++) { for (byte i = 0; i < WebBrowser.MaxRetries && htmlDocument == null; i++) {
htmlDocument = await WebBrowser.UrlGetToHtmlDocument(SteamCommunityURL + "/profiles/" + SteamID + "/gamecards/" + appID + "?l=english", Cookie).ConfigureAwait(false); htmlDocument = await WebBrowser.UrlGetToHtmlDocument(SteamCommunityURL + "/my/gamecards/" + appID).ConfigureAwait(false);
} }
if (htmlDocument == null) { if (htmlDocument == null) {
@@ -485,16 +496,16 @@ namespace ArchiSteamFarm {
} }
internal async Task<bool> MarkInventory() { internal async Task<bool> MarkInventory() {
if (SteamID == 0) { if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
return false; return false;
} }
HttpResponseMessage response = null; bool result = false;
for (byte i = 0; i < WebBrowser.MaxRetries && response == null; i++) { for (byte i = 0; i < WebBrowser.MaxRetries && !result; i++) {
response = await WebBrowser.UrlGet(SteamCommunityURL + "/profiles/" + SteamID + "/inventory", Cookie).ConfigureAwait(false); result = await WebBrowser.UrlGet(SteamCommunityURL + "/my/inventory").ConfigureAwait(false);
} }
if (response == null) { if (!result) {
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName); Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
return false; return false;
} }
@@ -507,8 +518,13 @@ namespace ArchiSteamFarm {
return false; return false;
} }
string sessionID; if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
if (!Cookie.TryGetValue("sessionid", out sessionID)) { return false;
}
string sessionID = WebBrowser.CookieContainer.GetCookieValue(SteamCommunityURL, "sessionid");
if (string.IsNullOrEmpty(sessionID)) {
Logging.LogNullError("sessionID");
return false; return false;
} }
@@ -517,12 +533,12 @@ namespace ArchiSteamFarm {
{ "sessionid", sessionID } { "sessionid", sessionID }
}; };
HttpResponseMessage response = null; bool result = false;
for (byte i = 0; i < WebBrowser.MaxRetries && response == null; i++) { for (byte i = 0; i < WebBrowser.MaxRetries && !result; i++) {
response = await WebBrowser.UrlPost(request, data, Cookie).ConfigureAwait(false); result = await WebBrowser.UrlPost(request, data).ConfigureAwait(false);
} }
if (response == null) { if (!result) {
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName); Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
return false; return false;
} }
@@ -530,9 +546,9 @@ namespace ArchiSteamFarm {
return true; return true;
} }
private async Task UnlockParentalAccount(string parentalPin) { private async Task<bool> UnlockParentalAccount(string parentalPin) {
if (string.IsNullOrEmpty(parentalPin) || parentalPin.Equals("0")) { if (string.IsNullOrEmpty(parentalPin) || parentalPin.Equals("0")) {
return; return true;
} }
Logging.LogGenericInfo("Unlocking parental account...", Bot.BotName); Logging.LogGenericInfo("Unlocking parental account...", Bot.BotName);
@@ -545,20 +561,23 @@ namespace ArchiSteamFarm {
HttpResponseMessage response = null; HttpResponseMessage response = null;
for (byte i = 0; i < WebBrowser.MaxRetries && response == null; i++) { for (byte i = 0; i < WebBrowser.MaxRetries && response == null; i++) {
response = await WebBrowser.UrlPost(request, data, Cookie, referer).ConfigureAwait(false); response = await WebBrowser.UrlPostToResponse(request, data, referer).ConfigureAwait(false);
} }
if (response == null) { if (response == null) {
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName); Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
return; return false;
} }
IEnumerable<string> setCookieValues; IEnumerable<string> setCookieValues;
if (!response.Headers.TryGetValues("Set-Cookie", out setCookieValues)) { if (!response.Headers.TryGetValues("Set-Cookie", out setCookieValues)) {
response.Dispose();
Logging.LogNullError("setCookieValues", Bot.BotName); Logging.LogNullError("setCookieValues", Bot.BotName);
return; return false;
} }
response.Dispose();
foreach (string setCookieValue in setCookieValues) { foreach (string setCookieValue in setCookieValues) {
if (!setCookieValue.Contains("steamparental=")) { if (!setCookieValue.Contains("steamparental=")) {
continue; continue;
@@ -571,12 +590,13 @@ namespace ArchiSteamFarm {
setCookie = setCookie.Substring(0, index); setCookie = setCookie.Substring(0, index);
} }
Cookie["steamparental"] = setCookie;
Logging.LogGenericInfo("Success!", Bot.BotName); Logging.LogGenericInfo("Success!", Bot.BotName);
return; WebBrowser.CookieContainer.Add(new Cookie("steamparental", setCookie, "/", "." + SteamCommunity));
return true;
} }
Logging.LogGenericWarning("Failed to unlock parental account!", Bot.BotName); Logging.LogGenericWarning("Failed to unlock parental account!", Bot.BotName);
return false;
} }
} }
} }

View File

@@ -38,6 +38,7 @@ namespace ArchiSteamFarm {
internal sealed class Bot { internal sealed class Bot {
private const ulong ArchiSCFarmGroup = 103582791440160998; private const ulong ArchiSCFarmGroup = 103582791440160998;
private const ushort CallbackSleep = 500; // In miliseconds private const ushort CallbackSleep = 500; // In miliseconds
private const ushort MaxSteamMessageLength = 2048;
internal static readonly Dictionary<string, Bot> Bots = new Dictionary<string, Bot>(); internal static readonly Dictionary<string, Bot> Bots = new Dictionary<string, Bot>();
@@ -63,7 +64,7 @@ namespace ArchiSteamFarm {
internal bool KeepRunning { get; private set; } internal bool KeepRunning { get; private set; }
private bool InvalidPassword, LoggedInElsewhere; private bool InvalidPassword, LoggedInElsewhere;
private string AuthCode, TwoFactorAuth; private string AuthCode, TwoFactorCode;
internal static async Task RefreshCMs(uint cellID) { internal static async Task RefreshCMs(uint cellID) {
bool initialized = false; bool initialized = false;
@@ -163,7 +164,7 @@ namespace ArchiSteamFarm {
} }
} }
ArchiHandler = new ArchiHandler(); ArchiHandler = new ArchiHandler(this);
SteamClient.AddHandler(ArchiHandler); SteamClient.AddHandler(ArchiHandler);
CallbackManager = new CallbackManager(SteamClient); CallbackManager = new CallbackManager(SteamClient);
@@ -187,6 +188,7 @@ namespace ArchiSteamFarm {
CallbackManager.Subscribe<SteamUser.LoggedOnCallback>(OnLoggedOn); CallbackManager.Subscribe<SteamUser.LoggedOnCallback>(OnLoggedOn);
CallbackManager.Subscribe<SteamUser.LoginKeyCallback>(OnLoginKey); CallbackManager.Subscribe<SteamUser.LoginKeyCallback>(OnLoginKey);
CallbackManager.Subscribe<SteamUser.UpdateMachineAuthCallback>(OnMachineAuth); CallbackManager.Subscribe<SteamUser.UpdateMachineAuthCallback>(OnMachineAuth);
CallbackManager.Subscribe<SteamUser.WebAPIUserNonceCallback>(OnWebAPIUserNonce);
CallbackManager.Subscribe<ArchiHandler.NotificationsCallback>(OnNotifications); CallbackManager.Subscribe<ArchiHandler.NotificationsCallback>(OnNotifications);
CallbackManager.Subscribe<ArchiHandler.OfflineMessageCallback>(OnOfflineMessage); CallbackManager.Subscribe<ArchiHandler.OfflineMessageCallback>(OnOfflineMessage);
@@ -196,16 +198,16 @@ namespace ArchiSteamFarm {
CardsFarmer = new CardsFarmer(this); CardsFarmer = new CardsFarmer(this);
Trading = new Trading(this); Trading = new Trading(this);
if (BotConfig.AcceptConfirmationsPeriod > 0) { if (AcceptConfirmationsTimer == null && BotConfig.AcceptConfirmationsPeriod > 0) {
AcceptConfirmationsTimer = new Timer( AcceptConfirmationsTimer = new Timer(
async e => await AcceptConfirmations().ConfigureAwait(false), async e => await AcceptConfirmations(true).ConfigureAwait(false),
null, null,
TimeSpan.FromMinutes(BotConfig.AcceptConfirmationsPeriod), // Delay TimeSpan.FromMinutes(BotConfig.AcceptConfirmationsPeriod), // Delay
TimeSpan.FromMinutes(BotConfig.AcceptConfirmationsPeriod) // Period TimeSpan.FromMinutes(BotConfig.AcceptConfirmationsPeriod) // Period
); );
} }
if (BotConfig.SendTradePeriod > 0) { if (SendItemsTimer == null && BotConfig.SendTradePeriod > 0) {
SendItemsTimer = new Timer( SendItemsTimer = new Timer(
async e => await ResponseSendTrade(BotConfig.SteamMasterID).ConfigureAwait(false), async e => await ResponseSendTrade(BotConfig.SteamMasterID).ConfigureAwait(false),
null, null,
@@ -222,7 +224,7 @@ namespace ArchiSteamFarm {
Start().Forget(); Start().Forget();
} }
internal async Task AcceptConfirmations(Confirmation.ConfirmationType allowedConfirmationType = Confirmation.ConfirmationType.Unknown) { internal async Task AcceptConfirmations(bool confirm, Confirmation.ConfirmationType allowedConfirmationType = Confirmation.ConfirmationType.Unknown) {
if (BotDatabase.SteamGuardAccount == null) { if (BotDatabase.SteamGuardAccount == null) {
return; return;
} }
@@ -242,10 +244,14 @@ namespace ArchiSteamFarm {
continue; continue;
} }
if (confirm) {
BotDatabase.SteamGuardAccount.AcceptConfirmation(confirmation); BotDatabase.SteamGuardAccount.AcceptConfirmation(confirmation);
} else {
BotDatabase.SteamGuardAccount.DenyConfirmation(confirmation);
}
} }
} catch (SteamGuardAccount.WGTokenInvalidException) { } catch (SteamGuardAccount.WGTokenInvalidException) {
Logging.LogGenericWarning("Accepting confirmation: Failed!", BotName); Logging.LogGenericWarning("Handling 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) { } catch (Exception e) {
@@ -261,12 +267,31 @@ namespace ArchiSteamFarm {
} }
} }
internal async Task RestartIfRunning() { internal async Task<bool> RefreshSession() {
if (!SteamClient.IsConnected) { if (!SteamClient.IsConnected) {
return; return false;
} }
await Start().ConfigureAwait(false); SteamUser.WebAPIUserNonceCallback callback;
try {
callback = await SteamUser.RequestWebAPIUserNonce();
} catch (Exception e) {
Logging.LogGenericException(e, BotName);
return false;
}
if (callback == null || callback.Result != EResult.OK || string.IsNullOrEmpty(callback.Nonce)) {
Start().Forget();
return false;
}
if (!ArchiWebHandler.Init(SteamClient, callback.Nonce, BotConfig.SteamParentalPIN)) {
Start().Forget();
return false;
}
return true;
} }
internal async Task OnFarmingFinished(bool farmedSomething) { internal async Task OnFarmingFinished(bool farmedSomething) {
@@ -285,17 +310,19 @@ namespace ArchiSteamFarm {
} }
if (!message.StartsWith("!")) { if (!message.StartsWith("!")) {
return await ResponseRedeem(steamID, BotName, message, true).ConfigureAwait(false); return await ResponseRedeem(steamID, message.Replace(",", Environment.NewLine), true).ConfigureAwait(false);
} }
if (!message.Contains(" ")) { if (!message.Contains(" ")) {
switch (message) { switch (message) {
case "!2fa": case "!2fa":
return Response2FA(steamID); return Response2FA(steamID);
case "!2fano":
return await Response2FAConfirm(steamID, false).ConfigureAwait(false);
case "!2faoff": case "!2faoff":
return Response2FAOff(steamID); return Response2FAOff(steamID);
case "!2faok": case "!2faok":
return await Response2FAOK(steamID).ConfigureAwait(false); return await Response2FAConfirm(steamID, true).ConfigureAwait(false);
case "!exit": case "!exit":
return ResponseExit(steamID); return ResponseExit(steamID);
case "!farm": case "!farm":
@@ -326,10 +353,12 @@ namespace ArchiSteamFarm {
switch (args[0]) { switch (args[0]) {
case "!2fa": case "!2fa":
return Response2FA(steamID, args[1]); return Response2FA(steamID, args[1]);
case "!2fano":
return await Response2FAConfirm(steamID, args[1], false).ConfigureAwait(false);
case "!2faoff": case "!2faoff":
return Response2FAOff(steamID, args[1]); return Response2FAOff(steamID, args[1]);
case "!2faok": case "!2faok":
return await Response2FAOK(steamID, args[1]).ConfigureAwait(false); return await Response2FAConfirm(steamID, args[1], true).ConfigureAwait(false);
case "!addlicense": case "!addlicense":
if (args.Length > 2) { if (args.Length > 2) {
return await ResponseAddLicense(steamID, args[1], args[2]).ConfigureAwait(false); return await ResponseAddLicense(steamID, args[1], args[2]).ConfigureAwait(false);
@@ -358,9 +387,9 @@ namespace ArchiSteamFarm {
} }
case "!redeem": case "!redeem":
if (args.Length > 2) { if (args.Length > 2) {
return await ResponseRedeem(steamID, args[1], args[2], false).ConfigureAwait(false); return await ResponseRedeem(steamID, args[1], args[2].Replace(",", Environment.NewLine), false).ConfigureAwait(false);
} else { } else {
return await ResponseRedeem(steamID, BotName, args[1], false).ConfigureAwait(false); return await ResponseRedeem(steamID, BotName, args[1].Replace(",", Environment.NewLine), false).ConfigureAwait(false);
} }
case "!start": case "!start":
return await ResponseStart(steamID, args[1]).ConfigureAwait(false); return await ResponseStart(steamID, args[1]).ConfigureAwait(false);
@@ -381,7 +410,7 @@ namespace ArchiSteamFarm {
} }
// 2FA tokens are expiring soon, use limiter only when we don't have any pending // 2FA tokens are expiring soon, use limiter only when we don't have any pending
if (TwoFactorAuth == null) { if (TwoFactorCode == null) {
await Program.LimitSteamRequestsAsync().ConfigureAwait(false); await Program.LimitSteamRequestsAsync().ConfigureAwait(false);
} }
@@ -432,7 +461,9 @@ namespace ArchiSteamFarm {
// But here we're dealing with WinAuth authenticator // But here we're dealing with WinAuth authenticator
Logging.LogGenericInfo("ASF requires a few more steps to complete authenticator import...", BotName); Logging.LogGenericInfo("ASF requires a few more steps to complete authenticator import...", BotName);
InitializeLoginAndPassword(); if (!InitializeLoginAndPassword()) {
return;
}
UserLogin userLogin = new UserLogin(BotConfig.SteamLogin, BotConfig.SteamPassword); UserLogin userLogin = new UserLogin(BotConfig.SteamLogin, BotConfig.SteamPassword);
LoginResult loginResult; LoginResult loginResult;
@@ -440,6 +471,9 @@ namespace ArchiSteamFarm {
switch (loginResult) { switch (loginResult) {
case LoginResult.Need2FA: case LoginResult.Need2FA:
userLogin.TwoFactorCode = Program.GetUserInput(Program.EUserInputType.TwoFactorAuthentication, BotName); userLogin.TwoFactorCode = Program.GetUserInput(Program.EUserInputType.TwoFactorAuthentication, BotName);
if (string.IsNullOrEmpty(userLogin.TwoFactorCode)) {
return;
}
break; break;
default: default:
Logging.LogGenericError("Unhandled situation: " + loginResult, BotName); Logging.LogGenericError("Unhandled situation: " + loginResult, BotName);
@@ -560,7 +594,7 @@ namespace ArchiSteamFarm {
} }
if (await ArchiWebHandler.SendTradeOffer(inventory, BotConfig.SteamMasterID, BotConfig.SteamTradeToken).ConfigureAwait(false)) { if (await ArchiWebHandler.SendTradeOffer(inventory, BotConfig.SteamMasterID, BotConfig.SteamTradeToken).ConfigureAwait(false)) {
await AcceptConfirmations(Confirmation.ConfirmationType.Trade).ConfigureAwait(false); await AcceptConfirmations(true, Confirmation.ConfirmationType.Trade).ConfigureAwait(false);
return "Trade offer sent successfully!"; return "Trade offer sent successfully!";
} else { } else {
return "Trade offer failed due to error!"; return "Trade offer failed due to error!";
@@ -635,7 +669,7 @@ namespace ArchiSteamFarm {
return bot.Response2FAOff(steamID); return bot.Response2FAOff(steamID);
} }
private async Task<string> Response2FAOK(ulong steamID) { private async Task<string> Response2FAConfirm(ulong steamID, bool confirm) {
if (steamID == 0 || !IsMaster(steamID)) { if (steamID == 0 || !IsMaster(steamID)) {
return null; return null;
} }
@@ -644,11 +678,11 @@ namespace ArchiSteamFarm {
return "That bot doesn't have ASF 2FA enabled!"; return "That bot doesn't have ASF 2FA enabled!";
} }
await AcceptConfirmations().ConfigureAwait(false); await AcceptConfirmations(confirm).ConfigureAwait(false);
return "Done!"; return "Done!";
} }
private static async Task<string> Response2FAOK(ulong steamID, string botName) { private static async Task<string> Response2FAConfirm(ulong steamID, string botName, bool confirm) {
if (steamID == 0 || string.IsNullOrEmpty(botName)) { if (steamID == 0 || string.IsNullOrEmpty(botName)) {
return null; return null;
} }
@@ -658,7 +692,7 @@ namespace ArchiSteamFarm {
return "Couldn't find any bot named " + botName + "!"; return "Couldn't find any bot named " + botName + "!";
} }
return await bot.Response2FAOK(steamID).ConfigureAwait(false); return await bot.Response2FAConfirm(steamID, confirm).ConfigureAwait(false);
} }
private static string ResponseExit(ulong steamID) { private static string ResponseExit(ulong steamID) {
@@ -670,8 +704,13 @@ namespace ArchiSteamFarm {
return null; return null;
} }
Environment.Exit(0); // Schedule the task after some time so user can receive response
return null; Task.Run(async () => {
await Utilities.SleepAsync(1000).ConfigureAwait(false);
Program.Exit();
}).Forget();
return "Done!";
} }
private string ResponseFarm(ulong steamID) { private string ResponseFarm(ulong steamID) {
@@ -727,105 +766,74 @@ namespace ArchiSteamFarm {
} }
StringBuilder response = new StringBuilder(); StringBuilder response = new StringBuilder();
using (StringReader reader = new StringReader(message)) { using (StringReader reader = new StringReader(message))
using (IEnumerator<Bot> iterator = Bots.Values.GetEnumerator()) {
string key = reader.ReadLine(); string key = reader.ReadLine();
IEnumerator<Bot> iterator = Bots.Values.GetEnumerator();
Bot currentBot = this; Bot currentBot = this;
while (key != null) { while (!string.IsNullOrEmpty(key) && currentBot != null) {
if (currentBot == null) {
break;
}
if (validate && !IsValidCdKey(key)) { if (validate && !IsValidCdKey(key)) {
key = reader.ReadLine(); key = reader.ReadLine(); // Next key
continue; continue; // Without changing the bot
} }
ArchiHandler.PurchaseResponseCallback result; if (currentBot.SteamClient.IsConnected) {
try { ArchiHandler.PurchaseResponseCallback result = await currentBot.ArchiHandler.RedeemKey(key).ConfigureAwait(false);
result = await currentBot.ArchiHandler.RedeemKey(key).ConfigureAwait(false); if (result != null) {
} catch (Exception e) { switch (result.PurchaseResult) {
Logging.LogGenericException(e, currentBot.BotName); case ArchiHandler.PurchaseResponseCallback.EPurchaseResult.DuplicatedKey:
break; case ArchiHandler.PurchaseResponseCallback.EPurchaseResult.InvalidKey:
} case ArchiHandler.PurchaseResponseCallback.EPurchaseResult.OK:
response.Append(Environment.NewLine + "<" + currentBot.BotName + "> Key: " + key + " | Status: " + result.PurchaseResult + " | Items: " + string.Join("", result.Items));
if (result == null) { key = reader.ReadLine(); // Next key
break; break; // Next bot (if needed)
}
var purchaseResult = result.PurchaseResult;
var items = result.Items;
switch (purchaseResult) {
case ArchiHandler.PurchaseResponseCallback.EPurchaseResult.AlreadyOwned: case ArchiHandler.PurchaseResponseCallback.EPurchaseResult.AlreadyOwned:
case ArchiHandler.PurchaseResponseCallback.EPurchaseResult.BaseGameRequired: case ArchiHandler.PurchaseResponseCallback.EPurchaseResult.BaseGameRequired:
case ArchiHandler.PurchaseResponseCallback.EPurchaseResult.OnCooldown: case ArchiHandler.PurchaseResponseCallback.EPurchaseResult.OnCooldown:
case ArchiHandler.PurchaseResponseCallback.EPurchaseResult.RegionLocked: case ArchiHandler.PurchaseResponseCallback.EPurchaseResult.RegionLocked:
response.Append(Environment.NewLine + "<" + currentBot.BotName + "> Key: " + key + " | Status: " + purchaseResult + " | Items: " + string.Join("", items)); response.Append(Environment.NewLine + "<" + currentBot.BotName + "> Key: " + key + " | Status: " + result.PurchaseResult + " | Items: " + string.Join("", result.Items));
if (!BotConfig.ForwardKeysToOtherBots) {
key = reader.ReadLine(); // Next key
break; // Next bot (if needed)
}
if (BotConfig.DistributeKeys) { if (BotConfig.DistributeKeys) {
do { break; // Next bot, without changing key
if (iterator.MoveNext()) {
currentBot = iterator.Current;
} else {
currentBot = null;
}
} while (currentBot == this);
if (!BotConfig.ForwardKeysToOtherBots) {
key = reader.ReadLine();
}
break;
}
if (!BotConfig.ForwardKeysToOtherBots) {
key = reader.ReadLine();
break;
} }
bool alreadyHandled = false; bool alreadyHandled = false;
foreach (Bot bot in Bots.Values) { foreach (Bot bot in Bots.Values) {
if (alreadyHandled) { if (bot == this || !bot.SteamClient.IsConnected) {
break;
}
if (bot == this) {
continue; continue;
} }
ArchiHandler.PurchaseResponseCallback otherResult; ArchiHandler.PurchaseResponseCallback otherResult = await bot.ArchiHandler.RedeemKey(key).ConfigureAwait(false);
try {
otherResult = await bot.ArchiHandler.RedeemKey(key).ConfigureAwait(false);
} catch (Exception e) {
Logging.LogGenericException(e, bot.BotName);
break; // We're done with this key
}
if (otherResult == null) { if (otherResult == null) {
break; // We're done with this key continue;
} }
var otherPurchaseResult = otherResult.PurchaseResult; switch (otherResult.PurchaseResult) {
var otherItems = otherResult.Items;
switch (otherPurchaseResult) {
case ArchiHandler.PurchaseResponseCallback.EPurchaseResult.OK:
alreadyHandled = true; // We're done with this key
response.Append(Environment.NewLine + "<" + bot.BotName + "> Key: " + key + " | Status: " + otherPurchaseResult + " | Items: " + string.Join("", otherItems));
break;
case ArchiHandler.PurchaseResponseCallback.EPurchaseResult.DuplicatedKey: case ArchiHandler.PurchaseResponseCallback.EPurchaseResult.DuplicatedKey:
case ArchiHandler.PurchaseResponseCallback.EPurchaseResult.InvalidKey: case ArchiHandler.PurchaseResponseCallback.EPurchaseResult.InvalidKey:
alreadyHandled = true; // This key doesn't work, don't try to redeem it anymore
response.Append(Environment.NewLine + "<" + bot.BotName + "> Key: " + key + " | Status: " + otherPurchaseResult + " | Items: " + string.Join("", otherItems));
break;
default:
response.Append(Environment.NewLine + "<" + bot.BotName + "> Key: " + key + " | Status: " + otherPurchaseResult + " | Items: " + string.Join("", otherItems));
break;
}
}
key = reader.ReadLine();
break;
case ArchiHandler.PurchaseResponseCallback.EPurchaseResult.OK: case ArchiHandler.PurchaseResponseCallback.EPurchaseResult.OK:
response.Append(Environment.NewLine + "<" + currentBot.BotName + "> Key: " + key + " | Status: " + purchaseResult + " | Items: " + string.Join("", items)); alreadyHandled = true; // This key is already handled, as we either redeemed it or we're sure it's dupe/invalid
break;
}
response.Append(Environment.NewLine + "<" + bot.BotName + "> Key: " + key + " | Status: " + otherResult.PurchaseResult + " | Items: " + string.Join("", otherResult.Items));
if (alreadyHandled) {
break;
}
}
key = reader.ReadLine(); // Next key
break; // Next bot (if needed)
}
}
}
if (BotConfig.DistributeKeys) { if (BotConfig.DistributeKeys) {
do { do {
if (iterator.MoveNext()) { if (iterator.MoveNext()) {
@@ -833,24 +841,7 @@ namespace ArchiSteamFarm {
} else { } else {
currentBot = null; currentBot = null;
} }
} while (currentBot == this); } while (currentBot == this || (currentBot != null && !currentBot.SteamClient.IsConnected));
}
key = reader.ReadLine();
break;
case ArchiHandler.PurchaseResponseCallback.EPurchaseResult.DuplicatedKey:
case ArchiHandler.PurchaseResponseCallback.EPurchaseResult.InvalidKey:
response.Append(Environment.NewLine + "<" + currentBot.BotName + "> Key: " + key + " | Status: " + purchaseResult + " | Items: " + string.Join("", items));
if (BotConfig.DistributeKeys && !BotConfig.ForwardKeysToOtherBots) {
do {
if (iterator.MoveNext()) {
currentBot = iterator.Current;
} else {
currentBot = null;
}
} while (currentBot == this);
}
key = reader.ReadLine();
break;
} }
} }
} }
@@ -900,8 +891,13 @@ namespace ArchiSteamFarm {
return null; return null;
} }
// Schedule the task after some time so user can receive response
Task.Run(async () => {
await Utilities.SleepAsync(1000).ConfigureAwait(false);
Program.Restart(); Program.Restart();
return null; }).Forget();
return "Done!";
} }
private async Task<string> ResponseAddLicense(ulong steamID, HashSet<uint> gameIDs) { private async Task<string> ResponseAddLicense(ulong steamID, HashSet<uint> gameIDs) {
@@ -911,11 +907,8 @@ namespace ArchiSteamFarm {
StringBuilder result = new StringBuilder(); StringBuilder result = new StringBuilder();
foreach (uint gameID in gameIDs) { foreach (uint gameID in gameIDs) {
SteamApps.FreeLicenseCallback callback; SteamApps.FreeLicenseCallback callback = await SteamApps.RequestFreeLicense(gameID);
try { if (callback == null) {
callback = await SteamApps.RequestFreeLicense(gameID);
} catch (Exception e) {
Logging.LogGenericException(e, BotName);
continue; continue;
} }
@@ -953,8 +946,8 @@ namespace ArchiSteamFarm {
return await bot.ResponseAddLicense(steamID, gamesToRedeem).ConfigureAwait(false); return await bot.ResponseAddLicense(steamID, gamesToRedeem).ConfigureAwait(false);
} }
private async Task<string> ResponseOwns(ulong steamID, string games) { private async Task<string> ResponseOwns(ulong steamID, string query) {
if (steamID == 0 || string.IsNullOrEmpty(games) || !IsMaster(steamID)) { if (steamID == 0 || string.IsNullOrEmpty(query) || !IsMaster(steamID)) {
return null; return null;
} }
@@ -963,37 +956,42 @@ namespace ArchiSteamFarm {
return "List of owned games is empty!"; return "List of owned games is empty!";
} }
// Check if this is uint
uint appID;
if (uint.TryParse(games, out appID)) {
string ownedName;
if (ownedGames.TryGetValue(appID, out ownedName)) {
return "Owned already: " + appID + " | " + ownedName;
} else {
return "Not owned yet: " + appID;
}
}
StringBuilder response = new StringBuilder(); StringBuilder response = new StringBuilder();
// This is a string string[] games = query.Split(',');
foreach (KeyValuePair<uint, string> game in ownedGames) { foreach (string game in games) {
if (game.Value.IndexOf(games, StringComparison.OrdinalIgnoreCase) < 0) { // Check if this is appID
uint appID;
if (uint.TryParse(game, out appID)) {
string ownedName;
if (ownedGames.TryGetValue(appID, out ownedName)) {
response.Append(Environment.NewLine + "Owned already: " + appID + " | " + ownedName);
} else {
response.Append(Environment.NewLine + "Not owned yet: " + appID);
}
continue; continue;
} }
response.AppendLine(Environment.NewLine + "Owned already: " + game.Key + " | " + game.Value); // This is a string, so check our entire library
foreach (KeyValuePair<uint, string> ownedGame in ownedGames) {
if (ownedGame.Value.IndexOf(game, StringComparison.OrdinalIgnoreCase) < 0) {
continue;
}
response.Append(Environment.NewLine + "Owned already: " + ownedGame.Key + " | " + ownedGame.Value);
}
} }
if (response.Length > 0) { if (response.Length > 0) {
return response.ToString(); return response.ToString();
} else { } else {
return "Not owned yet: " + games; return "Not owned yet: " + query;
} }
} }
private static async Task<string> ResponseOwns(ulong steamID, string botName, string games) { private static async Task<string> ResponseOwns(ulong steamID, string botName, string query) {
if (steamID == 0 || string.IsNullOrEmpty(botName) || string.IsNullOrEmpty(games)) { if (steamID == 0 || string.IsNullOrEmpty(botName) || string.IsNullOrEmpty(query)) {
return null; return null;
} }
@@ -1002,7 +1000,7 @@ namespace ArchiSteamFarm {
return "Couldn't find any bot named " + botName + "!"; return "Couldn't find any bot named " + botName + "!";
} }
return await bot.ResponseOwns(steamID, games).ConfigureAwait(false); return await bot.ResponseOwns(steamID, query).ConfigureAwait(false);
} }
private async Task<string> ResponsePlay(ulong steamID, HashSet<uint> gameIDs) { private async Task<string> ResponsePlay(ulong steamID, HashSet<uint> gameIDs) {
@@ -1129,7 +1127,11 @@ namespace ArchiSteamFarm {
private void HandleCallbacks() { private void HandleCallbacks() {
TimeSpan timeSpan = TimeSpan.FromMilliseconds(CallbackSleep); TimeSpan timeSpan = TimeSpan.FromMilliseconds(CallbackSleep);
while (KeepRunning || SteamClient.IsConnected) { while (KeepRunning || SteamClient.IsConnected) {
try {
CallbackManager.RunWaitCallbacks(timeSpan); CallbackManager.RunWaitCallbacks(timeSpan);
} catch (Exception e) {
Logging.LogGenericException(e, BotName);
}
} }
} }
@@ -1147,9 +1149,31 @@ namespace ArchiSteamFarm {
} }
if (new SteamID(steamID).IsChatAccount) { if (new SteamID(steamID).IsChatAccount) {
SteamFriends.SendChatRoomMessage(steamID, EChatEntryType.ChatMsg, message); SendMessageToChannel(steamID, message);
} else { } else {
SteamFriends.SendChatMessage(steamID, EChatEntryType.ChatMsg, message); SendMessageToUser(steamID, message);
}
}
private void SendMessageToChannel(ulong steamID, string message) {
if (steamID == 0 || string.IsNullOrEmpty(message)) {
return;
}
for (int i = 0; i < message.Length; i += MaxSteamMessageLength - 6) {
string messagePart = (i > 0 ? "..." : "") + message.Substring(i, Math.Min(MaxSteamMessageLength - 6, message.Length - i)) + (MaxSteamMessageLength - 6 < message.Length - i ? "..." : "");
SteamFriends.SendChatRoomMessage(steamID, EChatEntryType.ChatMsg, messagePart);
}
}
private void SendMessageToUser(ulong steamID, string message) {
if (steamID == 0 || string.IsNullOrEmpty(message)) {
return;
}
for (int i = 0; i < message.Length; i += MaxSteamMessageLength - 6) {
string messagePart = (i > 0 ? "..." : "") + message.Substring(i, Math.Min(MaxSteamMessageLength - 6, message.Length - i)) + (MaxSteamMessageLength - 6 < message.Length - i ? "..." : "");
SteamFriends.SendChatMessage(steamID, EChatEntryType.ChatMsg, messagePart);
} }
} }
@@ -1160,7 +1184,9 @@ namespace ArchiSteamFarm {
Logging.LogGenericInfo("Linking new ASF MobileAuthenticator...", BotName); Logging.LogGenericInfo("Linking new ASF MobileAuthenticator...", BotName);
InitializeLoginAndPassword(); if (!InitializeLoginAndPassword()) {
return;
}
UserLogin userLogin = new UserLogin(BotConfig.SteamLogin, BotConfig.SteamPassword); UserLogin userLogin = new UserLogin(BotConfig.SteamLogin, BotConfig.SteamPassword);
LoginResult loginResult; LoginResult loginResult;
@@ -1168,6 +1194,9 @@ namespace ArchiSteamFarm {
switch (loginResult) { switch (loginResult) {
case LoginResult.NeedEmail: case LoginResult.NeedEmail:
userLogin.EmailCode = Program.GetUserInput(Program.EUserInputType.SteamGuard, BotName); userLogin.EmailCode = Program.GetUserInput(Program.EUserInputType.SteamGuard, BotName);
if (string.IsNullOrEmpty(userLogin.EmailCode)) {
return;
}
break; break;
default: default:
Logging.LogGenericError("Unhandled situation: " + loginResult, BotName); Logging.LogGenericError("Unhandled situation: " + loginResult, BotName);
@@ -1182,6 +1211,9 @@ namespace ArchiSteamFarm {
switch (linkResult) { switch (linkResult) {
case AuthenticatorLinker.LinkResult.MustProvidePhoneNumber: case AuthenticatorLinker.LinkResult.MustProvidePhoneNumber:
authenticatorLinker.PhoneNumber = Program.GetUserInput(Program.EUserInputType.PhoneNumber, BotName); authenticatorLinker.PhoneNumber = Program.GetUserInput(Program.EUserInputType.PhoneNumber, BotName);
if (string.IsNullOrEmpty(authenticatorLinker.PhoneNumber)) {
return;
}
break; break;
default: default:
Logging.LogGenericError("Unhandled situation: " + linkResult, BotName); Logging.LogGenericError("Unhandled situation: " + linkResult, BotName);
@@ -1191,13 +1223,21 @@ namespace ArchiSteamFarm {
BotDatabase.SteamGuardAccount = authenticatorLinker.LinkedAccount; BotDatabase.SteamGuardAccount = authenticatorLinker.LinkedAccount;
AuthenticatorLinker.FinalizeResult finalizeResult = authenticatorLinker.FinalizeAddAuthenticator(Program.GetUserInput(Program.EUserInputType.SMS, BotName)); string sms = Program.GetUserInput(Program.EUserInputType.SMS, BotName);
if (string.IsNullOrEmpty(sms)) {
return;
}
AuthenticatorLinker.FinalizeResult finalizeResult = authenticatorLinker.FinalizeAddAuthenticator(sms);
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; return;
} }
// Ensure that we also save changes made by finalization step (if any)
BotDatabase.Save();
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(Program.EUserInputType.RevocationCode, BotName, BotDatabase.SteamGuardAccount.RevocationCode); Program.GetUserInput(Program.EUserInputType.RevocationCode, BotName, BotDatabase.SteamGuardAccount.RevocationCode);
} }
@@ -1224,16 +1264,24 @@ namespace ArchiSteamFarm {
SteamFriends.JoinChat(BotConfig.SteamMasterClanID); SteamFriends.JoinChat(BotConfig.SteamMasterClanID);
} }
private void InitializeLoginAndPassword() { private bool InitializeLoginAndPassword() {
if (string.IsNullOrEmpty(BotConfig.SteamLogin)) { if (string.IsNullOrEmpty(BotConfig.SteamLogin)) {
BotConfig.SteamLogin = Program.GetUserInput(Program.EUserInputType.Login, BotName); BotConfig.SteamLogin = Program.GetUserInput(Program.EUserInputType.Login, BotName);
if (string.IsNullOrEmpty(BotConfig.SteamLogin)) {
return false;
}
} }
if (string.IsNullOrEmpty(BotConfig.SteamPassword) && string.IsNullOrEmpty(BotDatabase.LoginKey)) { if (string.IsNullOrEmpty(BotConfig.SteamPassword) && string.IsNullOrEmpty(BotDatabase.LoginKey)) {
BotConfig.SteamPassword = Program.GetUserInput(Program.EUserInputType.Password, BotName); BotConfig.SteamPassword = Program.GetUserInput(Program.EUserInputType.Password, BotName);
if (string.IsNullOrEmpty(BotConfig.SteamPassword)) {
return false;
} }
} }
return true;
}
private void OnConnected(SteamClient.ConnectedCallback callback) { private void OnConnected(SteamClient.ConnectedCallback callback) {
if (callback == null) { if (callback == null) {
return; return;
@@ -1262,10 +1310,18 @@ namespace ArchiSteamFarm {
} }
} }
InitializeLoginAndPassword(); if (!InitializeLoginAndPassword()) {
Stop();
return;
}
Logging.LogGenericInfo("Logging in...", BotName); Logging.LogGenericInfo("Logging in...", BotName);
// If we have ASF 2FA enabled, we can always provide TwoFactorCode, and save a request
if (BotDatabase.SteamGuardAccount != null) {
TwoFactorCode = BotDatabase.SteamGuardAccount.GenerateSteamGuardCode();
}
// TODO: Please remove me immediately after https://github.com/SteamRE/SteamKit/issues/254 gets fixed // TODO: Please remove me immediately after https://github.com/SteamRE/SteamKit/issues/254 gets fixed
if (Program.GlobalConfig.HackIgnoreMachineID) { if (Program.GlobalConfig.HackIgnoreMachineID) {
Logging.LogGenericWarning("Using workaround for broken GenerateMachineID()!", BotName); Logging.LogGenericWarning("Using workaround for broken GenerateMachineID()!", BotName);
@@ -1275,7 +1331,7 @@ namespace ArchiSteamFarm {
AuthCode = AuthCode, AuthCode = AuthCode,
LoginID = LoginID, LoginID = LoginID,
LoginKey = BotDatabase.LoginKey, LoginKey = BotDatabase.LoginKey,
TwoFactorCode = TwoFactorAuth, TwoFactorCode = TwoFactorCode,
SentryFileHash = sentryHash, SentryFileHash = sentryHash,
ShouldRememberPassword = true ShouldRememberPassword = true
}); });
@@ -1288,7 +1344,7 @@ namespace ArchiSteamFarm {
AuthCode = AuthCode, AuthCode = AuthCode,
LoginID = LoginID, LoginID = LoginID,
LoginKey = BotDatabase.LoginKey, LoginKey = BotDatabase.LoginKey,
TwoFactorCode = TwoFactorAuth, TwoFactorCode = TwoFactorCode,
SentryFileHash = sentryHash, SentryFileHash = sentryHash,
ShouldRememberPassword = true ShouldRememberPassword = true
}); });
@@ -1300,7 +1356,6 @@ namespace ArchiSteamFarm {
} }
Logging.LogGenericInfo("Disconnected from Steam!", BotName); Logging.LogGenericInfo("Disconnected from Steam!", BotName);
ArchiWebHandler.OnDisconnected();
CardsFarmer.StopFarming().Forget(); CardsFarmer.StopFarming().Forget();
// If we initiated disconnect, do not attempt to reconnect // If we initiated disconnect, do not attempt to reconnect
@@ -1336,7 +1391,7 @@ namespace ArchiSteamFarm {
Logging.LogGenericInfo("Reconnecting...", BotName); Logging.LogGenericInfo("Reconnecting...", BotName);
// 2FA tokens are expiring soon, use limiter only when we don't have any pending // 2FA tokens are expiring soon, use limiter only when we don't have any pending
if (TwoFactorAuth == null) { if (TwoFactorCode == null) {
await Program.LimitSteamRequestsAsync().ConfigureAwait(false); await Program.LimitSteamRequestsAsync().ConfigureAwait(false);
} }
@@ -1354,15 +1409,6 @@ namespace ArchiSteamFarm {
return; return;
} }
for (byte i = 0; i < WebBrowser.MaxRetries && !ArchiWebHandler.IsInitialized; i++) {
await Utilities.SleepAsync(1000).ConfigureAwait(false);
}
if (!ArchiWebHandler.IsInitialized) {
Logging.LogGenericWarning("Reached timeout while waiting for ArchiWebHandler to initialize!");
return;
}
bool acceptedSomething = false; bool acceptedSomething = false;
foreach (KeyValue guestPass in callback.GuestPasses) { foreach (KeyValue guestPass in callback.GuestPasses) {
ulong gid = guestPass["gid"].AsUnsignedLong(); ulong gid = guestPass["gid"].AsUnsignedLong();
@@ -1500,12 +1546,20 @@ namespace ArchiSteamFarm {
switch (callback.Result) { switch (callback.Result) {
case EResult.AccountLogonDenied: case EResult.AccountLogonDenied:
AuthCode = Program.GetUserInput(Program.EUserInputType.SteamGuard, BotName); AuthCode = Program.GetUserInput(Program.EUserInputType.SteamGuard, BotName);
if (string.IsNullOrEmpty(AuthCode)) {
Stop();
return;
}
break; break;
case EResult.AccountLoginDeniedNeedTwoFactor: case EResult.AccountLoginDeniedNeedTwoFactor:
if (BotDatabase.SteamGuardAccount == null) { if (BotDatabase.SteamGuardAccount == null) {
TwoFactorAuth = Program.GetUserInput(Program.EUserInputType.TwoFactorAuthentication, BotName); TwoFactorCode = Program.GetUserInput(Program.EUserInputType.TwoFactorAuthentication, BotName);
if (string.IsNullOrEmpty(TwoFactorCode)) {
Stop();
return;
}
} else { } else {
TwoFactorAuth = BotDatabase.SteamGuardAccount.GenerateSteamGuardCode(); Logging.LogGenericWarning("2FA code was invalid despite of using ASF 2FA. Invalid authenticator or bad timing?", BotName);
} }
break; break;
case EResult.InvalidPassword: case EResult.InvalidPassword:
@@ -1524,24 +1578,29 @@ namespace ArchiSteamFarm {
string maFilePath = Path.Combine(Program.ConfigDirectory, callback.ClientSteamID.ConvertToUInt64() + ".maFile"); string maFilePath = Path.Combine(Program.ConfigDirectory, callback.ClientSteamID.ConvertToUInt64() + ".maFile");
if (File.Exists(maFilePath)) { if (File.Exists(maFilePath)) {
ImportAuthenticator(maFilePath); ImportAuthenticator(maFilePath);
} else if (TwoFactorAuth == null && BotConfig.UseAsfAsMobileAuthenticator) { } else if (TwoFactorCode == null && BotConfig.UseAsfAsMobileAuthenticator) {
LinkMobileAuthenticator(); LinkMobileAuthenticator();
} }
} }
// Reset one-time-only access tokens // Reset one-time-only access tokens
AuthCode = TwoFactorAuth = null; AuthCode = TwoFactorCode = null;
ResetGamesPlayed(); ResetGamesPlayed();
if (string.IsNullOrEmpty(BotConfig.SteamParentalPIN)) { if (string.IsNullOrEmpty(BotConfig.SteamParentalPIN)) {
BotConfig.SteamParentalPIN = Program.GetUserInput(Program.EUserInputType.SteamParentalPIN, BotName); BotConfig.SteamParentalPIN = Program.GetUserInput(Program.EUserInputType.SteamParentalPIN, BotName);
if (string.IsNullOrEmpty(BotConfig.SteamParentalPIN)) {
Stop();
return;
}
} }
if (!await ArchiWebHandler.Init(SteamClient, callback.WebAPIUserNonce, BotConfig.SteamParentalPIN).ConfigureAwait(false)) { if (!ArchiWebHandler.Init(SteamClient, callback.WebAPIUserNonce, BotConfig.SteamParentalPIN)) {
await RestartIfRunning().ConfigureAwait(false); if (!await RefreshSession().ConfigureAwait(false)) {
return; return;
} }
}
if (BotConfig.DismissInventoryNotifications) { if (BotConfig.DismissInventoryNotifications) {
ArchiWebHandler.MarkInventory().Forget(); ArchiWebHandler.MarkInventory().Forget();
@@ -1592,7 +1651,6 @@ namespace ArchiSteamFarm {
return; return;
} }
try {
int fileSize; int fileSize;
byte[] sentryHash; byte[] sentryHash;
@@ -1617,10 +1675,13 @@ namespace ArchiSteamFarm {
Result = EResult.OK, Result = EResult.OK,
LastError = 0, LastError = 0,
OneTimePassword = callback.OneTimePassword, OneTimePassword = callback.OneTimePassword,
SentryFileHash = sentryHash, SentryFileHash = sentryHash
}); });
} catch (Exception e) { }
Logging.LogGenericException(e, BotName);
private void OnWebAPIUserNonce(SteamUser.WebAPIUserNonceCallback callback) {
if (callback == null) {
return;
} }
} }

View File

@@ -99,14 +99,14 @@ namespace ArchiSteamFarm {
internal HashSet<uint> GamesPlayedWhileIdle { get; private set; } = new HashSet<uint>() { 0 }; internal HashSet<uint> GamesPlayedWhileIdle { get; private set; } = new HashSet<uint>() { 0 };
internal static BotConfig Load(string path) { internal static BotConfig Load(string filePath) {
if (string.IsNullOrEmpty(path) || !File.Exists(path)) { if (string.IsNullOrEmpty(filePath) || !File.Exists(filePath)) {
return null; return null;
} }
BotConfig botConfig; BotConfig botConfig;
try { try {
botConfig = JsonConvert.DeserializeObject<BotConfig>(File.ReadAllText(path)); botConfig = JsonConvert.DeserializeObject<BotConfig>(File.ReadAllText(filePath));
} catch (Exception e) { } catch (Exception e) {
Logging.LogGenericException(e); Logging.LogGenericException(e);
return null; return null;

View File

@@ -53,7 +53,7 @@ namespace ArchiSteamFarm {
Bot = bot; Bot = bot;
if (Program.GlobalConfig.IdleFarmingPeriod > 0) { if (Timer == null && Program.GlobalConfig.IdleFarmingPeriod > 0) {
Timer = new Timer( Timer = new Timer(
async e => await CheckGamesForFarming().ConfigureAwait(false), async e => await CheckGamesForFarming().ConfigureAwait(false),
null, null,
@@ -103,6 +103,7 @@ namespace ArchiSteamFarm {
bool farmedSomething = false; bool farmedSomething = false;
do {
// Now the algorithm used for farming depends on whether account is restricted or not // Now the algorithm used for farming depends on whether account is restricted or not
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);
@@ -141,18 +142,12 @@ namespace ArchiSteamFarm {
} }
} }
} }
} while (await IsAnythingToFarm().ConfigureAwait(false));
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)) {
StartFarming().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);
} }
@@ -207,14 +202,6 @@ namespace ArchiSteamFarm {
} }
private async Task<bool> IsAnythingToFarm() { private async Task<bool> IsAnythingToFarm() {
if (NowFarming) {
return true;
}
if (await Bot.ArchiWebHandler.ReconnectIfNeeded().ConfigureAwait(false)) {
return false;
}
Logging.LogGenericInfo("Checking badges...", Bot.BotName); Logging.LogGenericInfo("Checking badges...", Bot.BotName);
// Find the number of badge pages // Find the number of badge pages
@@ -229,10 +216,13 @@ namespace ArchiSteamFarm {
HtmlNodeCollection htmlNodeCollection = htmlDocument.DocumentNode.SelectNodes("//a[@class='pagelink']"); HtmlNodeCollection htmlNodeCollection = htmlDocument.DocumentNode.SelectNodes("//a[@class='pagelink']");
if (htmlNodeCollection != null && htmlNodeCollection.Count > 0) { if (htmlNodeCollection != null && htmlNodeCollection.Count > 0) {
HtmlNode htmlNode = htmlNodeCollection[htmlNodeCollection.Count - 1]; HtmlNode htmlNode = htmlNodeCollection[htmlNodeCollection.Count - 1];
if (!byte.TryParse(htmlNode.InnerText, out maxPages)) { string lastPage = htmlNode.InnerText;
if (!string.IsNullOrEmpty(lastPage)) {
if (!byte.TryParse(lastPage, out maxPages)) {
maxPages = 1; // Should never happen maxPages = 1; // Should never happen
} }
} }
}
GamesToFarm.Clear(); GamesToFarm.Clear();
@@ -242,17 +232,13 @@ namespace ArchiSteamFarm {
Logging.LogGenericInfo("Checking other pages...", Bot.BotName); Logging.LogGenericInfo("Checking other pages...", Bot.BotName);
List<Task> tasks = new List<Task>(maxPages - 1); List<Task> tasks = new List<Task>(maxPages - 1);
for (byte page = 2; page <= maxPages; page++) { for (byte page = 2; page <= maxPages; page++) {
byte currentPage = page; // We need a copy of variable being passed when in for loops byte currentPage = page; // We need a copy of variable being passed when in for loops, as loop will proceed before task is launched
tasks.Add(CheckPage(currentPage)); tasks.Add(CheckPage(currentPage));
} }
await Task.WhenAll(tasks).ConfigureAwait(false); await Task.WhenAll(tasks).ConfigureAwait(false);
} }
if (GamesToFarm.Count == 0) { return GamesToFarm.Count > 0;
return false;
}
return true;
} }
private void CheckPage(HtmlDocument htmlDocument) { private void CheckPage(HtmlDocument htmlDocument) {
@@ -362,7 +348,6 @@ namespace ArchiSteamFarm {
HtmlNode htmlNode = htmlDocument.DocumentNode.SelectSingleNode("//span[@class='progress_info_bold']"); HtmlNode htmlNode = htmlDocument.DocumentNode.SelectSingleNode("//span[@class='progress_info_bold']");
if (htmlNode == null) { if (htmlNode == null) {
await Bot.ArchiWebHandler.ReconnectIfNeeded().ConfigureAwait(false);
return null; return null;
} }

View File

@@ -36,9 +36,10 @@ namespace ArchiSteamFarm {
Experimental Experimental
} }
internal const byte DefaultHttpTimeout = 60;
private const byte DefaultMaxFarmingTime = 10; private const byte DefaultMaxFarmingTime = 10;
private const byte DefaultFarmingDelay = 5; private const byte DefaultFarmingDelay = 5;
private const byte DefaultHttpTimeout = 60;
private const ushort DefaultWCFPort = 1242; private const ushort DefaultWCFPort = 1242;
private const ProtocolType DefaultSteamProtocol = ProtocolType.Tcp; private const ProtocolType DefaultSteamProtocol = ProtocolType.Tcp;
@@ -48,6 +49,9 @@ namespace ArchiSteamFarm {
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
internal bool Debug { get; private set; } = false; internal bool Debug { get; private set; } = false;
[JsonProperty(Required = Required.DisallowNull)]
internal bool Headless { get; private set; } = false;
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
internal bool AutoUpdates { get; private set; } = true; internal bool AutoUpdates { get; private set; } = true;
@@ -117,6 +121,10 @@ namespace ArchiSteamFarm {
return null; return null;
} }
if (globalConfig == null) {
return null;
}
// SK2 supports only TCP and UDP steam protocols // SK2 supports only TCP and UDP steam protocols
// Ensure that user can't screw this up // Ensure that user can't screw this up
switch (globalConfig.SteamProtocol) { switch (globalConfig.SteamProtocol) {

View File

@@ -82,17 +82,21 @@ namespace ArchiSteamFarm {
private static Timer AutoUpdatesTimer; private static Timer AutoUpdatesTimer;
private static EMode Mode = EMode.Normal; private static EMode Mode = EMode.Normal;
private static WebBrowser WebBrowser;
internal static async Task CheckForUpdate() { internal static async Task CheckForUpdate() {
string oldExeFile = ExecutableFile + ".old"; string oldExeFile = ExecutableFile + ".old";
// We booted successfully so we can now remove old exe file // We booted successfully so we can now remove old exe file
if (File.Exists(oldExeFile)) { if (File.Exists(oldExeFile)) {
// It's entirely possible that old process is still running, allow at least a second before trying to remove the file
await Utilities.SleepAsync(1000).ConfigureAwait(false);
try { try {
File.Delete(oldExeFile); File.Delete(oldExeFile);
} catch (Exception e) { } catch (Exception e) {
Logging.LogGenericException(e); Logging.LogGenericException(e);
return; Logging.LogGenericError("Could not remove old ASF binary, please remove " + oldExeFile + " manually in order for update function to work!");
} }
} }
@@ -170,6 +174,11 @@ namespace ArchiSteamFarm {
return; return;
} }
if (File.Exists(oldExeFile)) {
Logging.LogGenericWarning("Refusing to proceed with auto update as old " + oldExeFile + " binary could not be removed, please remove it manually");
return;
}
// Auto update logic starts here // Auto update logic starts here
if (releaseResponse.Assets == null) { if (releaseResponse.Assets == null) {
Logging.LogGenericWarning("Could not proceed with update because that version doesn't include assets!"); Logging.LogGenericWarning("Could not proceed with update because that version doesn't include assets!");
@@ -247,13 +256,19 @@ namespace ArchiSteamFarm {
Restart(); Restart();
} }
internal static void Exit(int exitCode = 0) {
WCF.StopServer();
Environment.Exit(exitCode);
}
internal static void Restart() { internal static void Restart() {
try { try {
Process.Start(ExecutableFile, string.Join(" ", Environment.GetCommandLineArgs().Skip(1))); Process.Start(ExecutableFile, string.Join(" ", Environment.GetCommandLineArgs().Skip(1)));
Environment.Exit(0);
} catch (Exception e) { } catch (Exception e) {
Logging.LogGenericException(e); Logging.LogGenericException(e);
} }
Exit();
} }
internal static async Task LimitSteamRequestsAsync() { internal static async Task LimitSteamRequestsAsync() {
@@ -269,6 +284,11 @@ namespace ArchiSteamFarm {
return null; return null;
} }
if (GlobalConfig.Headless) {
Logging.LogGenericWarning("Received a request for user input, but process is running in headless mode!");
return null;
}
string result; string result;
lock (ConsoleLock) { lock (ConsoleLock) {
ConsoleIsBusy = true; ConsoleIsBusy = true;
@@ -295,7 +315,7 @@ namespace ArchiSteamFarm {
Console.Write((string.IsNullOrEmpty(botName) ? "" : "<" + botName + "> ") + "Please enter steam parental PIN: "); Console.Write((string.IsNullOrEmpty(botName) ? "" : "<" + botName + "> ") + "Please enter steam parental PIN: ");
break; break;
case EUserInputType.RevocationCode: case EUserInputType.RevocationCode:
Console.Write((string.IsNullOrEmpty(botName) ? "" : "<" + botName + "> ") + "PLEASE WRITE DOWN YOUR REVOCATION CODE: " + extraInformation); Console.WriteLine((string.IsNullOrEmpty(botName) ? "" : "<" + botName + "> ") + "PLEASE WRITE DOWN YOUR REVOCATION CODE: " + extraInformation);
Console.Write("Hit enter once ready..."); Console.Write("Hit enter once ready...");
break; break;
case EUserInputType.TwoFactorAuthentication: case EUserInputType.TwoFactorAuthentication:
@@ -337,19 +357,21 @@ namespace ArchiSteamFarm {
if (GlobalConfig == null) { if (GlobalConfig == null) {
Logging.LogGenericError("Global config could not be loaded, please make sure that ASF.json exists and is valid!"); Logging.LogGenericError("Global config could not be loaded, please make sure that ASF.json exists and is valid!");
Thread.Sleep(5000); Thread.Sleep(5000);
Environment.Exit(1); Exit(1);
} }
GlobalDatabase = GlobalDatabase.Load(); GlobalDatabase = GlobalDatabase.Load();
if (GlobalDatabase == null) { if (GlobalDatabase == null) {
Logging.LogGenericError("Global database could not be loaded!"); Logging.LogGenericError("Global database could not be loaded!");
Thread.Sleep(5000); Thread.Sleep(5000);
Environment.Exit(1); Exit(1);
} }
ArchiWebHandler.Init(); ArchiWebHandler.Init();
WebBrowser.Init(); WebBrowser.Init();
WCF.Init(); WCF.Init();
WebBrowser = new WebBrowser("Main");
} }
private static void ParseArgs(string[] args) { private static void ParseArgs(string[] args) {
@@ -445,7 +467,7 @@ namespace ArchiSteamFarm {
// If we ran ASF as a client, we're done by now // If we ran ASF as a client, we're done by now
if (Mode == EMode.Client) { if (Mode == EMode.Client) {
Environment.Exit(0); Exit();
} }
// From now on it's server mode // From now on it's server mode
@@ -454,7 +476,7 @@ namespace ArchiSteamFarm {
if (!Directory.Exists(ConfigDirectory)) { if (!Directory.Exists(ConfigDirectory)) {
Logging.LogGenericError("Config directory doesn't exist!"); Logging.LogGenericError("Config directory doesn't exist!");
Thread.Sleep(5000); Thread.Sleep(5000);
Environment.Exit(1); Exit(1);
} }
CheckForUpdate().Wait(); CheckForUpdate().Wait();
@@ -494,7 +516,7 @@ namespace ArchiSteamFarm {
ShutdownResetEvent.WaitOne(); ShutdownResetEvent.WaitOne();
// We got a signal to shutdown // We got a signal to shutdown
Environment.Exit(0); Exit();
} }
} }
} }

View File

@@ -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.2.1")] [assembly: AssemblyVersion("2.0.2.8")]
[assembly: AssemblyFileVersion("2.0.2.1")] [assembly: AssemblyFileVersion("2.0.2.8")]

View File

@@ -84,19 +84,12 @@ namespace ArchiSteamFarm {
return; return;
} }
List<Task> tasks = new List<Task>(); await tradeOffers.ForEachAsync(ParseTrade).ConfigureAwait(false);
foreach (Steam.TradeOffer tradeOffer in tradeOffers) { await Bot.AcceptConfirmations(true, Confirmation.ConfirmationType.Trade).ConfigureAwait(false);
if (tradeOffer.trade_offer_state == Steam.TradeOffer.ETradeOfferState.Active) {
tasks.Add(Task.Run(async () => await ParseTrade(tradeOffer).ConfigureAwait(false)));
}
}
await Task.WhenAll(tasks).ConfigureAwait(false);
await Bot.AcceptConfirmations(Confirmation.ConfirmationType.Trade).ConfigureAwait(false);
} }
private async Task ParseTrade(Steam.TradeOffer tradeOffer) { private async Task ParseTrade(Steam.TradeOffer tradeOffer) {
if (tradeOffer == null) { if (tradeOffer == null || tradeOffer.trade_offer_state != Steam.TradeOffer.ETradeOfferState.Active) {
return; return;
} }
@@ -105,12 +98,33 @@ namespace ArchiSteamFarm {
return; return;
} }
if (tradeOffer.items_to_give.Count == 0 || tradeOffer.OtherSteamID64 == Bot.BotConfig.SteamMasterID) { if (ShouldAcceptTrade(tradeOffer)) {
Logging.LogGenericInfo("Accepting trade: " + tradeID, Bot.BotName); Logging.LogGenericInfo("Accepting trade: " + tradeID, Bot.BotName);
await Bot.ArchiWebHandler.AcceptTradeOffer(tradeID).ConfigureAwait(false); await Bot.ArchiWebHandler.AcceptTradeOffer(tradeID).ConfigureAwait(false);
} else { } else {
Logging.LogGenericInfo("Ignoring trade: " + tradeID, Bot.BotName); Logging.LogGenericInfo("Ignoring trade: " + tradeID, Bot.BotName);
} }
} }
private bool ShouldAcceptTrade(Steam.TradeOffer tradeOffer) {
if (tradeOffer == null) {
return false;
}
// Always accept trades when we're not losing anything
if (tradeOffer.items_to_give.Count == 0) {
return true;
}
// Always accept trades from SteamMasterID
if (tradeOffer.OtherSteamID64 != 0 && tradeOffer.OtherSteamID64 == Bot.BotConfig.SteamMasterID) {
return true;
}
// TODO: Add optional SteamTradeMatcher integration here
// If no rule above matched this trade, reject it
return false;
}
} }
} }

View File

@@ -22,14 +22,47 @@
*/ */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks; 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 void Forget(this Task task) { }
internal static async Task SleepAsync(int miliseconds) { internal static Task ForEachAsync<T>(this IEnumerable<T> sequence, Func<T, Task> action) {
await Task.Delay(miliseconds).ConfigureAwait(false); return Task.WhenAll(sequence.Select(action));
}
internal static string GetCookieValue(this CookieContainer cookieContainer, string URL, string name) {
if (string.IsNullOrEmpty(URL) || string.IsNullOrEmpty(name)) {
return null;
}
CookieCollection cookies = cookieContainer.GetCookies(new Uri(URL));
if (cookies == null || cookies.Count == 0) {
return null;
}
foreach (Cookie cookie in cookies) {
if (!cookie.Name.Equals(name, StringComparison.Ordinal)) {
continue;
}
return cookie.Value;
}
return null;
}
internal static Task SleepAsync(int miliseconds) {
if (miliseconds < 0) {
return Task.FromResult(true);
}
return Task.Delay(miliseconds);
} }
internal static uint GetCharCountInString(string s, char c) { internal static uint GetCharCountInString(string s, char c) {

View File

@@ -44,6 +44,9 @@ namespace ArchiSteamFarm {
internal static void Init() { internal static void Init() {
if (string.IsNullOrEmpty(Program.GlobalConfig.WCFHostname)) { if (string.IsNullOrEmpty(Program.GlobalConfig.WCFHostname)) {
Program.GlobalConfig.WCFHostname = Program.GetUserInput(Program.EUserInputType.WCFHostname); Program.GlobalConfig.WCFHostname = Program.GetUserInput(Program.EUserInputType.WCFHostname);
if (string.IsNullOrEmpty(Program.GlobalConfig.WCFHostname)) {
return;
}
} }
URL = "http://" + Program.GlobalConfig.WCFHostname + ":" + Program.GlobalConfig.WCFPort + "/ASF"; URL = "http://" + Program.GlobalConfig.WCFHostname + ":" + Program.GlobalConfig.WCFPort + "/ASF";

View File

@@ -23,37 +23,31 @@
*/ */
using HtmlAgilityPack; using HtmlAgilityPack;
using Newtonsoft.Json;
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.IO;
using System.Net; using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Xml; using System.Xml;
namespace ArchiSteamFarm { namespace ArchiSteamFarm {
internal static class WebBrowser { internal sealed class WebBrowser {
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)
private 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 private 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
private const byte MaxIdleTime = 15; // In seconds, how long socket is allowed to stay in CLOSE_WAIT state after there are no connections to it private const byte MaxIdleTime = 15; // In seconds, how long socket is allowed to stay in CLOSE_WAIT state after there are no connections to it
private static readonly string DefaultUserAgent = "ArchiSteamFarm/" + Program.Version; private static readonly string DefaultUserAgent = "ArchiSteamFarm/" + Program.Version;
private static readonly HttpClient HttpClient = new HttpClient(new HttpClientHandler {
UseCookies = false internal readonly CookieContainer CookieContainer = new CookieContainer();
}) {
Timeout = TimeSpan.FromSeconds(60) private readonly HttpClient HttpClient;
}; private readonly string Identifier;
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
// Any request can override that on as-needed basis (see: RequestOptions.FakeUserAgent)
HttpClient.DefaultRequestHeaders.UserAgent.ParseAdd(DefaultUserAgent);
// Set max connection limit from default of 2 to desired value // Set max connection limit from default of 2 to desired value
ServicePointManager.DefaultConnectionLimit = MaxConnections; ServicePointManager.DefaultConnectionLimit = MaxConnections;
@@ -63,67 +57,114 @@ namespace ArchiSteamFarm {
// Don't use Expect100Continue, we're sure about our POSTs, save some TCP packets // Don't use Expect100Continue, we're sure about our POSTs, save some TCP packets
ServicePointManager.Expect100Continue = false; ServicePointManager.Expect100Continue = false;
// Reuse ports if possible #if !__MonoCS__
// TODO: Mono doesn't support that feature yet // Reuse ports if possible (since .NET 4.6+)
//ServicePointManager.ReusePort = true; //ServicePointManager.ReusePort = true;
#endif
} }
internal static async Task<HttpResponseMessage> UrlGet(string request, Dictionary<string, string> cookies = null, string referer = null) { internal WebBrowser(string identifier) {
if (string.IsNullOrEmpty(identifier)) {
return;
}
Identifier = identifier;
HttpClientHandler httpClientHandler = new HttpClientHandler {
AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip,
CookieContainer = CookieContainer
};
HttpClient = new HttpClient(httpClientHandler) {
Timeout = TimeSpan.FromSeconds(Program.GlobalConfig.HttpTimeout)
};
// Most web services expect that UserAgent is set, so we declare it globally
HttpClient.DefaultRequestHeaders.UserAgent.ParseAdd(DefaultUserAgent);
}
internal async Task<bool> UrlGet(string request, string referer = null) {
if (string.IsNullOrEmpty(request)) {
return false;
}
HttpResponseMessage response = await UrlGetToResponse(request, referer).ConfigureAwait(false);
if (response == null) {
return false;
}
response.Dispose();
return true;
}
internal async Task<bool> UrlPost(string request, Dictionary<string, string> data = null, string referer = null) {
if (string.IsNullOrEmpty(request)) {
return false;
}
HttpResponseMessage response = await UrlPostToResponse(request, data, referer).ConfigureAwait(false);
if (response == null) {
return false;
}
response.Dispose();
return true;
}
internal async Task<HttpResponseMessage> UrlGetToResponse(string request, string referer = null) {
if (string.IsNullOrEmpty(request)) { if (string.IsNullOrEmpty(request)) {
return null; return null;
} }
return await UrlRequest(request, HttpMethod.Get, null, cookies, referer).ConfigureAwait(false); return await UrlRequest(request, HttpMethod.Get, null, referer).ConfigureAwait(false);
} }
internal static async Task<HttpResponseMessage> UrlPost(string request, Dictionary<string, string> data = null, Dictionary<string, string> cookies = null, string referer = null) { internal async Task<HttpResponseMessage> UrlPostToResponse(string request, Dictionary<string, string> data = null, string referer = null) {
if (string.IsNullOrEmpty(request)) { if (string.IsNullOrEmpty(request)) {
return null; return null;
} }
return await UrlRequest(request, HttpMethod.Post, data, cookies, referer).ConfigureAwait(false); return await UrlRequest(request, HttpMethod.Post, data, referer).ConfigureAwait(false);
} }
internal static async Task<string> UrlGetToContent(string request, Dictionary<string, string> cookies = null, string referer = null) { internal async Task<string> UrlGetToContent(string request, string referer = null) {
if (string.IsNullOrEmpty(request)) { if (string.IsNullOrEmpty(request)) {
return null; return null;
} }
HttpResponseMessage httpResponse = await UrlGet(request, cookies, referer).ConfigureAwait(false); HttpResponseMessage httpResponse = await UrlGetToResponse(request, referer).ConfigureAwait(false);
if (httpResponse == null) { if (httpResponse == null) {
return null; return null;
} }
if (httpResponse.Content == null) { string result = await httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false);
return null; httpResponse.Dispose();
return result;
} }
return await httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false); internal async Task<Stream> UrlGetToStream(string request, string referer = null) {
}
internal static async Task<Stream> UrlGetToStream(string request, Dictionary<string, string> cookies = null, string referer = null) {
if (string.IsNullOrEmpty(request)) { if (string.IsNullOrEmpty(request)) {
return null; return null;
} }
HttpResponseMessage httpResponse = await UrlGet(request, cookies, referer).ConfigureAwait(false); HttpResponseMessage httpResponse = await UrlGetToResponse(request, referer).ConfigureAwait(false);
if (httpResponse == null) { if (httpResponse == null) {
return null; return null;
} }
if (httpResponse.Content == null) { Stream result = await httpResponse.Content.ReadAsStreamAsync().ConfigureAwait(false);
return null; httpResponse.Dispose();
return result;
} }
return await httpResponse.Content.ReadAsStreamAsync().ConfigureAwait(false); internal async Task<HtmlDocument> UrlGetToHtmlDocument(string request, string referer = null) {
}
internal static async Task<HtmlDocument> UrlGetToHtmlDocument(string request, Dictionary<string, string> cookies = null, string referer = null) {
if (string.IsNullOrEmpty(request)) { if (string.IsNullOrEmpty(request)) {
return null; return null;
} }
string content = await UrlGetToContent(request, cookies, referer).ConfigureAwait(false); string content = await UrlGetToContent(request, referer).ConfigureAwait(false);
if (string.IsNullOrEmpty(content)) { if (string.IsNullOrEmpty(content)) {
return null; return null;
} }
@@ -135,12 +176,12 @@ namespace ArchiSteamFarm {
return htmlDocument; return htmlDocument;
} }
internal static async Task<JObject> UrlGetToJObject(string request, Dictionary<string, string> cookies = null, string referer = null) { internal async Task<JObject> UrlGetToJObject(string request, string referer = null) {
if (string.IsNullOrEmpty(request)) { if (string.IsNullOrEmpty(request)) {
return null; return null;
} }
string content = await UrlGetToContent(request, cookies, referer).ConfigureAwait(false); string content = await UrlGetToContent(request, referer).ConfigureAwait(false);
if (string.IsNullOrEmpty(content)) { if (string.IsNullOrEmpty(content)) {
return null; return null;
} }
@@ -149,20 +190,20 @@ namespace ArchiSteamFarm {
try { try {
jObject = JObject.Parse(content); jObject = JObject.Parse(content);
} catch (Exception e) { } catch (JsonException e) {
Logging.LogGenericException(e); Logging.LogGenericException(e, Identifier);
return null; return null;
} }
return jObject; return jObject;
} }
internal static async Task<XmlDocument> UrlGetToXML(string request, Dictionary<string, string> cookies = null, string referer = null) { internal async Task<XmlDocument> UrlGetToXML(string request, string referer = null) {
if (string.IsNullOrEmpty(request)) { if (string.IsNullOrEmpty(request)) {
return null; return null;
} }
string content = await UrlGetToContent(request, cookies, referer).ConfigureAwait(false); string content = await UrlGetToContent(request, referer).ConfigureAwait(false);
if (string.IsNullOrEmpty(content)) { if (string.IsNullOrEmpty(content)) {
return null; return null;
} }
@@ -172,37 +213,33 @@ namespace ArchiSteamFarm {
try { try {
xmlDocument.LoadXml(content); xmlDocument.LoadXml(content);
} catch (XmlException e) { } catch (XmlException e) {
Logging.LogGenericException(e); Logging.LogGenericException(e, Identifier);
return null; return null;
} }
return xmlDocument; return xmlDocument;
} }
private static async Task<HttpResponseMessage> UrlRequest(string request, HttpMethod httpMethod, Dictionary<string, string> data = null, Dictionary<string, string> cookies = null, string referer = null) { private async Task<HttpResponseMessage> UrlRequest(string request, HttpMethod httpMethod, Dictionary<string, string> data = null, string referer = null) {
if (string.IsNullOrEmpty(request) || httpMethod == null) { if (string.IsNullOrEmpty(request) || httpMethod == null) {
return null; return null;
} }
if (request.StartsWith("https://") && Program.GlobalConfig.ForceHttp) {
return null;
}
HttpResponseMessage responseMessage; HttpResponseMessage responseMessage;
using (HttpRequestMessage requestMessage = new HttpRequestMessage(httpMethod, request)) { using (HttpRequestMessage requestMessage = new HttpRequestMessage(httpMethod, request)) {
if (data != null && data.Count > 0) { if (data != null && data.Count > 0) {
try { try {
requestMessage.Content = new FormUrlEncodedContent(data); requestMessage.Content = new FormUrlEncodedContent(data);
} catch (UriFormatException e) { } catch (UriFormatException e) {
Logging.LogGenericException(e); Logging.LogGenericException(e, Identifier);
return null; return null;
} }
} }
if (cookies != null && cookies.Count > 0) {
StringBuilder cookieHeader = new StringBuilder();
foreach (KeyValuePair<string, string> cookie in cookies) {
cookieHeader.Append(cookie.Key + "=" + cookie.Value + ";");
}
requestMessage.Headers.Add("Cookie", cookieHeader.ToString());
}
if (!string.IsNullOrEmpty(referer)) { if (!string.IsNullOrEmpty(referer)) {
requestMessage.Headers.Referrer = new Uri(referer); requestMessage.Headers.Referrer = new Uri(referer);
} }
@@ -214,15 +251,14 @@ namespace ArchiSteamFarm {
} }
} }
if (responseMessage == null) { if (responseMessage == null || !responseMessage.IsSuccessStatusCode) {
return null;
}
if (!responseMessage.IsSuccessStatusCode) {
if (Debugging.IsDebugBuild || Program.GlobalConfig.Debug) { if (Debugging.IsDebugBuild || Program.GlobalConfig.Debug) {
Logging.LogGenericError("Request: " + request + "failed!"); Logging.LogGenericError("Request: " + request + " failed!", Identifier);
Logging.LogGenericError("Status code: " + responseMessage.StatusCode); if (responseMessage != null) {
Logging.LogGenericError("Content: " + Environment.NewLine + await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false)); Logging.LogGenericError("Status code: " + responseMessage.StatusCode, Identifier);
Logging.LogGenericError("Content: " + Environment.NewLine + await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false), Identifier);
responseMessage.Dispose();
}
} }
return null; return null;
} }

View File

@@ -1,5 +1,6 @@
{ {
"Debug": false, "Debug": false,
"Headless": false,
"AutoUpdates": true, "AutoUpdates": true,
"UpdateChannel": 1, "UpdateChannel": 1,
"SteamProtocol": 6, "SteamProtocol": 6,

View File

@@ -54,9 +54,9 @@ namespace ConfigGenerator {
internal virtual void Remove() { internal virtual void Remove() {
string queryPath = Path.GetFileNameWithoutExtension(FilePath); string queryPath = Path.GetFileNameWithoutExtension(FilePath);
lock (FilePath) { lock (FilePath) {
foreach (var configFile in Directory.EnumerateFiles(Program.ConfigDirectory, queryPath + ".*")) { foreach (string botFile in Directory.EnumerateFiles(Program.ConfigDirectory, queryPath + ".*")) {
try { try {
File.Delete(configFile); File.Delete(botFile);
} catch (Exception e) { } catch (Exception e) {
Logging.LogGenericException(e); Logging.LogGenericException(e);
} }
@@ -72,9 +72,9 @@ namespace ConfigGenerator {
string queryPath = Path.GetFileNameWithoutExtension(FilePath); string queryPath = Path.GetFileNameWithoutExtension(FilePath);
lock (FilePath) { lock (FilePath) {
foreach (var file in Directory.EnumerateFiles(Program.ConfigDirectory, queryPath + ".*")) { foreach (string botFile in Directory.EnumerateFiles(Program.ConfigDirectory, queryPath + ".*")) {
try { try {
File.Move(file, Path.Combine(Program.ConfigDirectory, botName + Path.GetExtension(file))); File.Move(botFile, Path.Combine(Program.ConfigDirectory, botName + Path.GetExtension(botFile)));
} catch (Exception e) { } catch (Exception e) {
Logging.LogGenericException(e); Logging.LogGenericException(e);
} }

View File

@@ -98,7 +98,6 @@ namespace ConfigGenerator {
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
public List<uint> GamesPlayedWhileIdle { get; set; } = new List<uint>(); public List<uint> GamesPlayedWhileIdle { get; set; } = new List<uint>();
internal static BotConfig Load(string filePath) { internal static BotConfig Load(string filePath) {
if (string.IsNullOrEmpty(filePath)) { if (string.IsNullOrEmpty(filePath)) {
return null; return null;
@@ -116,6 +115,10 @@ namespace ConfigGenerator {
return new BotConfig(filePath); return new BotConfig(filePath);
} }
if (botConfig == null) {
return new BotConfig(filePath);
}
botConfig.FilePath = filePath; botConfig.FilePath = filePath;
return botConfig; return botConfig;

View File

@@ -48,6 +48,9 @@ namespace ConfigGenerator {
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
public bool Debug { get; set; } = false; public bool Debug { get; set; } = false;
[JsonProperty(Required = Required.DisallowNull)]
public bool Headless { get; set; } = false;
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
public bool AutoUpdates { get; set; } = true; public bool AutoUpdates { get; set; } = true;
@@ -120,6 +123,10 @@ namespace ConfigGenerator {
return new GlobalConfig(filePath); return new GlobalConfig(filePath);
} }
if (globalConfig == null) {
return new GlobalConfig(filePath);
}
globalConfig.FilePath = filePath; globalConfig.FilePath = filePath;
// SK2 supports only TCP and UDP steam protocols // SK2 supports only TCP and UDP steam protocols

66
cc.sh Executable file
View File

@@ -0,0 +1,66 @@
#!/bin/bash
set -eu
BUILD="Release"
CLEAN=0
MONO_ARGS=("--aot" "--llvm" "--server" "-O=all")
XBUILD_ARGS=("/nologo")
BINARIES=("ArchiSteamFarm/bin/Release/ArchiSteamFarm.exe")
SOLUTION="ArchiSteamFarm.sln"
PRINT_USAGE() {
echo "Usage: $0 [--clean] [debug/release]"
exit 1
}
for ARG in "$@"; do
case "$ARG" in
release|Release) BUILD="Release" ;;
debug|Debug) BUILD="Debug" ;;
--clean) CLEAN=1 ;;
*) PRINT_USAGE
esac
done
XBUILD_ARGS+=("/p:Configuration=$BUILD")
cd "$(dirname "$(readlink -f "$0")")"
if [[ -d ".git" ]] && hash git &>/dev/null; then
git pull
fi
if [[ ! -f "$SOLUTION" ]]; then
echo "ERROR: $SOLUTION could not be found!"
exit 1
fi
if hash nuget &>/dev/null; then
nuget restore "$SOLUTION"
fi
if [[ "$CLEAN" -eq 1 ]]; then
rm -rf out
xbuild "${XBUILD_ARGS[@]}" "/t:Clean" "$SOLUTION"
fi
xbuild "${XBUILD_ARGS[@]}" "$SOLUTION"
if [[ ! -f "${BINARIES[0]}" ]]; then
echo "ERROR: ${BINARIES[0]} binary could not be found!"
fi
# If it's release build, use Mono AOT for output binaries
if [[ "$BUILD" = "Release" ]]; then
for BINARY in "${BINARIES[@]}"; do
if [[ ! -f "$BINARY" ]]; then
continue
fi
mono "${MONO_ARGS[@]}" "$BINARY"
done
fi
echo
echo "Compilation finished successfully! :)"

28
run.sh Executable file
View File

@@ -0,0 +1,28 @@
#!/bin/bash
set -eu
BUILD="Release"
MONO_ARGS=("--llvm" "--server" "-O=all")
PRINT_USAGE() {
echo "Usage: $0 [debug/release]"
exit 1
}
for ARG in "$@"; do
case "$ARG" in
release|Release) BUILD="Release" ;;
debug|Debug) BUILD="Debug" ;;
*) PRINT_USAGE
esac
done
BINARY="ArchiSteamFarm/bin/$BUILD/ArchiSteamFarm.exe"
if [[ ! -f "$BINARY" ]]; then
echo "ERROR: $BINARY could not be found!"
exit 1
fi
mono "${MONO_ARGS[@]}" "$BINARY"