Compare commits

...

14 Commits

Author SHA1 Message Date
JustArchi
9f4ed24704 And copy it... 2015-12-18 07:01:03 +01:00
JustArchi
015c6b7bdf Add minimal.xml to VS project 2015-12-18 06:59:47 +01:00
JustArchi
a17c1fc35a VS please 2015-12-18 06:56:33 +01:00
JustArchi
19e46ce78d Misc 2015-12-18 06:54:53 +01:00
JustArchi
cf6ee3b60d Switch to new async methods 2015-12-17 22:04:13 +01:00
JustArchi
488003993f Do not wait longer than 5 sec 2015-12-17 22:00:24 +01:00
JustArchi
c64a6fabbc SteamAuth sync 2015-12-17 21:59:32 +01:00
JustArchi
eb2751861d Don't forget to reset status back to false after the test, closes #34 2015-12-17 20:57:03 +01:00
JustArchi
48a1cf1189 Handle expired StartFarming too 2015-12-16 22:14:53 +01:00
JustArchi
98e1d51a48 Handle expired sessionIDs for main accounts 2015-12-16 22:05:42 +01:00
JustArchi
5e22c832a2 Do not reject friend invites, group invites, and trades, by default, closes #33 2015-12-16 20:09:31 +01:00
JustArchi
6523b30f10 Always use LoginID, closes #32 2015-12-16 20:01:40 +01:00
JustArchi
8fefa7c5af Use random UniqueID for LoginID 2015-12-16 10:21:13 +01:00
JustArchi
b6a5b8942c Actually do not wait 25 minutes after expired login keys 2015-12-16 09:39:03 +01:00
13 changed files with 261 additions and 56 deletions

3
.gitignore vendored
View File

@@ -2,9 +2,10 @@
## ArchiSteamFarm
#################
# Ignore all config files, apart from example.xml
# Ignore all config files, apart from ones we want to include
ArchiSteamFarm/config/*
!ArchiSteamFarm/config/example.xml
!ArchiSteamFarm/config/minimal.xml
#################
## Eclipse

View File

@@ -123,6 +123,9 @@
<Content Include="config\example.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="config\minimal.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SteamAuth\SteamAuth.csproj">
@@ -139,6 +142,7 @@
<PostBuildEvent Condition=" '$(OS)' != 'Unix' ">if $(ConfigurationName) == Release (
mkdir "$(TargetDir)out" "$(TargetDir)out\config"
copy "$(TargetDir)config\example.xml" "$(TargetDir)out\config"
copy "$(TargetDir)config\minimal.xml" "$(TargetDir)out\config"
"$(SolutionDir)tools\ILMerge.exe" /out:"$(TargetDir)out\ASF.exe" "$(TargetDir)$(TargetName).exe" "$(TargetDir)*.dll" /target:exe /targetplatform:v4,C:\Windows\Microsoft.NET\Framework64\v4.0.30319 /wildcards
del "$(TargetDir)out\ASF.pdb"
)</PostBuildEvent>

View File

@@ -150,6 +150,31 @@ namespace ArchiSteamFarm {
}
}
internal async Task<bool?> IsLoggedIn() {
if (SteamID == 0) {
return false;
}
HtmlDocument htmlDocument = await WebBrowser.UrlGetToHtmlDocument("http://steamcommunity.com/my/profile", SteamCookieDictionary).ConfigureAwait(false);
if (htmlDocument == null) {
return null;
}
HtmlNode htmlNode = htmlDocument.DocumentNode.SelectSingleNode("//span[@id='account_pulldown']");
return htmlNode != null;
}
internal async Task<bool> ReconnectIfNeeded() {
bool? isLoggedIn = await IsLoggedIn().ConfigureAwait(false);
if (isLoggedIn.HasValue && !isLoggedIn.Value) {
Logging.LogGenericInfo(Bot.BotName, "Reconnecting because our sessionID expired!");
Bot.SteamClient.Disconnect(); // Bot will handle reconnect
return true;
}
return false;
}
internal List<SteamTradeOffer> GetTradeOffers() {
if (ApiKey == null) {
return null;

View File

@@ -45,7 +45,6 @@ namespace ArchiSteamFarm {
private bool LoggedInElsewhere = false;
private bool IsRunning = false;
private bool IsBeingUsedAsPrimaryAccount = false;
private string AuthCode, LoginKey, TwoFactorAuth;
internal ArchiHandler ArchiHandler { get; private set; }
@@ -70,7 +69,7 @@ namespace ArchiSteamFarm {
internal bool CardDropsRestricted { get; private set; } = false;
internal bool UseAsfAsMobileAuthenticator { get; private set; } = false;
internal bool ShutdownOnFarmingFinished { get; private set; } = false;
internal HashSet<uint> Blacklist { get; private set; } = new HashSet<uint> { 303700, 335590, 368020 };
internal HashSet<uint> Blacklist { get; private set; } = new HashSet<uint> { 303700, 335590, 368020, 425280 };
internal bool Statistics { get; private set; } = true;
private static bool IsValidCdKey(string key) {
@@ -161,15 +160,15 @@ namespace ArchiSteamFarm {
var fireAndForget = Task.Run(async () => await Start().ConfigureAwait(false));
}
internal void AcceptAllConfirmations() {
internal async Task AcceptAllConfirmations() {
if (SteamGuardAccount == null) {
return;
}
SteamGuardAccount.RefreshSession();
await SteamGuardAccount.RefreshSessionAsync().ConfigureAwait(false);
try {
foreach (Confirmation confirmation in SteamGuardAccount.FetchConfirmations()) {
foreach (Confirmation confirmation in await SteamGuardAccount.FetchConfirmationsAsync().ConfigureAwait(false)) {
if (SteamGuardAccount.AcceptConfirmation(confirmation)) {
Logging.LogGenericInfo(BotName, "Accepting confirmation: Success!");
} else {
@@ -542,7 +541,8 @@ namespace ArchiSteamFarm {
SteamPassword = Program.GetUserInput(BotName, Program.EUserInputType.Password);
}
SteamUser.LogOnDetails logOnDetails = new SteamUser.LogOnDetails {
// TODO: We should use SteamUser.LogOn with proper LoginID once https://github.com/SteamRE/SteamKit/pull/217 gets merged
ArchiHandler.HackedLogOn(Program.UniqueID, new SteamUser.LogOnDetails {
Username = SteamLogin,
Password = SteamPassword,
AuthCode = AuthCode,
@@ -550,14 +550,7 @@ namespace ArchiSteamFarm {
TwoFactorCode = TwoFactorAuth,
SentryFileHash = sentryHash,
ShouldRememberPassword = true
};
if (!IsBeingUsedAsPrimaryAccount) {
SteamUser.LogOn(logOnDetails);
} else {
// TODO: We should use SteamUser.LogOn with proper LoginID once https://github.com/SteamRE/SteamKit/pull/217 gets merged
ArchiHandler.HackedLogOn(0xBAADF00D, logOnDetails);
}
});
}
private async void OnDisconnected(SteamClient.DisconnectedCallback callback) {
@@ -604,13 +597,11 @@ namespace ArchiSteamFarm {
SteamID steamID = friend.SteamID;
switch (steamID.AccountType) {
case EAccountType.Clan:
ArchiHandler.DeclineClanInvite(steamID);
// TODO: Accept clan invites from master?
break;
default:
if (steamID == SteamMasterID) {
SteamFriends.AddFriend(steamID);
} else {
SteamFriends.RemoveFriend(steamID);
}
break;
}
@@ -712,12 +703,9 @@ namespace ArchiSteamFarm {
Logging.LogGenericInfo(BotName, "Logged off of Steam: " + callback.Result);
switch (callback.Result) {
case EResult.LogonSessionReplaced:
Logging.LogGenericInfo(BotName, "This is primary account, changing logic alt -> main");
IsBeingUsedAsPrimaryAccount = true;
break;
case EResult.AlreadyLoggedInElsewhere:
case EResult.LoggedInElsewhere:
case EResult.LogonSessionReplaced:
LoggedInElsewhere = true;
break;
}
@@ -741,15 +729,18 @@ namespace ArchiSteamFarm {
}
break;
case EResult.InvalidPassword:
Logging.LogGenericWarning(BotName, "Unable to login to Steam: " + result + ", will retry after a longer while");
Logging.LogGenericWarning(BotName, "Unable to login to Steam: " + result);
await Stop().ConfigureAwait(false);
// InvalidPassword means that we must assume login key has expired
LoginKey = null;
File.Delete(LoginKeyFile);
// InvalidPassword also means that we might get captcha or other network-based throttling
await Utilities.SleepAsync(25 * 60 * 1000).ConfigureAwait(false); // Captcha disappears after around 20 minutes, so we make it 25
// InvalidPassword means usually that login key has expired, if we used it
if (!string.IsNullOrEmpty(LoginKey)) {
LoginKey = null;
File.Delete(LoginKeyFile);
Logging.LogGenericInfo(BotName, "Removed expired login key, reconnecting...");
} else { // If we didn't use login key, InvalidPassword usually means we got captcha or other network-based throttling
Logging.LogGenericInfo(BotName, "Will retry after 25 minutes...");
await Utilities.SleepAsync(25 * 60 * 1000).ConfigureAwait(false); // Captcha disappears after around 20 minutes, so we make it 25
}
// After all of that, try again
await Start().ConfigureAwait(false);

View File

@@ -155,10 +155,16 @@ namespace ArchiSteamFarm {
return;
}
NowFarming = false;
Logging.LogGenericInfo(Bot.BotName, "Farming is possible!");
await Semaphore.WaitAsync().ConfigureAwait(false);
if (await Bot.ArchiWebHandler.ReconnectIfNeeded().ConfigureAwait(false)) {
Semaphore.Release();
return;
}
Logging.LogGenericInfo(Bot.BotName, "Checking badges...");
// Find the number of badge pages
@@ -305,7 +311,7 @@ namespace ArchiSteamFarm {
Logging.LogGenericInfo(Bot.BotName, "Sending signal to stop farming");
FarmResetEvent.Set();
while (NowFarming) {
for (var i = 0; i < 5 && NowFarming; i++) {
Logging.LogGenericInfo(Bot.BotName, "Waiting for reaction...");
await Utilities.SleepAsync(1000).ConfigureAwait(false);
}
@@ -323,15 +329,22 @@ namespace ArchiSteamFarm {
}
private async Task<bool?> ShouldFarm(ulong appID) {
bool? result = null;
HtmlDocument gamePageDocument = await Bot.ArchiWebHandler.GetGameCardsPage(appID).ConfigureAwait(false);
if (gamePageDocument != null) {
HtmlNode gamePageNode = gamePageDocument.DocumentNode.SelectSingleNode("//span[@class='progress_info_bold']");
if (gamePageNode != null) {
result = !gamePageNode.InnerText.Contains("No card drops");
}
if (appID == 0) {
return false;
}
return result;
HtmlDocument htmlDocument = await Bot.ArchiWebHandler.GetGameCardsPage(appID).ConfigureAwait(false);
if (htmlDocument == null) {
return null;
}
HtmlNode htmlNode = htmlDocument.DocumentNode.SelectSingleNode("//span[@class='progress_info_bold']");
if (htmlNode == null) {
await Bot.ArchiWebHandler.ReconnectIfNeeded().ConfigureAwait(false);
return null;
}
return !htmlNode.InnerText.Contains("No card drops");
}
private async Task<bool> Farm(uint appID) {

View File

@@ -55,6 +55,7 @@ namespace ArchiSteamFarm {
private static readonly object ConsoleLock = new object();
//private static readonly string ExeName = AssemblyName.Name + ".exe";
internal static readonly uint UniqueID = (uint) Utilities.Random.Next();
internal static readonly string Version = AssemblyName.Version.ToString();
internal static bool ConsoleIsBusy = false;

View File

@@ -62,7 +62,7 @@ namespace ArchiSteamFarm {
}
await Task.WhenAll(tasks).ConfigureAwait(false);
Bot.AcceptAllConfirmations();
await Bot.AcceptAllConfirmations().ConfigureAwait(false);
}
private async Task ParseTrade(SteamTradeOffer tradeOffer) {
@@ -75,20 +75,11 @@ namespace ArchiSteamFarm {
return;
}
bool success, tradeAccepted;
if (tradeOffer.items_to_give.Count == 0 || tradeOffer.OtherSteamID64 == Bot.SteamMasterID) {
tradeAccepted = true;
Logging.LogGenericInfo(Bot.BotName, "Accepting trade: " + tradeID);
success = await Bot.ArchiWebHandler.AcceptTradeOffer(tradeID).ConfigureAwait(false);
await Bot.ArchiWebHandler.AcceptTradeOffer(tradeID).ConfigureAwait(false);
} else {
tradeAccepted = false;
Logging.LogGenericInfo(Bot.BotName, "Rejecting trade: " + tradeID);
success = Bot.ArchiWebHandler.DeclineTradeOffer(tradeID);
}
if (tradeAccepted && success) {
// Do whatever we want with success
Logging.LogGenericInfo(Bot.BotName, "Ignoring trade: " + tradeID);
}
}
}

View File

@@ -22,11 +22,14 @@
*/
using System;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace ArchiSteamFarm {
internal static class Utilities {
internal static readonly Random Random = new Random();
internal static async Task SleepAsync(int miliseconds) {
await Task.Delay(miliseconds).ConfigureAwait(false);
}

View File

@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<!-- Every bot should have it's own unique .xml configuration file, this is example on which you can base on -->
<!-- This is full-fledged example config, you may be also interested in minimal.xml for bare minimum one -->
<!-- Default values used in config match default ASF values when given config property is not found -->
<!-- Notice, if you use special characters reserved for XML, you should escape them -->
<!-- Escape table: [& - &amp;] | [" - &quot;] | [' - &apos;] | [< - &lt;] | [> - &gt;] -->
@@ -71,7 +72,7 @@
<!-- Comma-separated list of IDs that should not be considered for farming -->
<!-- Default value includes appIDs that are wrongly appearing on the profile, e.g. Monster Summer Sale -->
<!-- TIP: Most likely you don't want to change it -->
<Blacklist type="HashSet(uint)" value="303700,335590,368020"/>
<Blacklist type="HashSet(uint)" value="303700,335590,368020,425280"/>
<!-- This switch enables statistics for me - bot will join Archi's SC Farm group and chat -->
<!-- Consider leaving it at "true", this way I can check how many running bots are active -->

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<!-- This is minimalistic config to "just make ASF work" -->
<!-- For full-fledged config, please take a look at example.xml -->
<Enabled type="bool" value="false"/>
<SteamLogin type="string" value="null"/>
<SteamPassword type="string" value="null"/>
</configuration>

View File

@@ -6,6 +6,7 @@ using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace SteamAuth
{
@@ -171,6 +172,56 @@ namespace SteamAuth
return ret.ToArray();
}
public async Task<Confirmation[]> FetchConfirmationsAsync()
{
string url = this.GenerateConfirmationURL();
CookieContainer cookies = new CookieContainer();
this.Session.AddCookies(cookies);
string response = await SteamWeb.RequestAsync(url, "GET", null, cookies);
/*So you're going to see this abomination and you're going to be upset.
It's understandable. But the thing is, regex for HTML -- while awful -- makes this way faster than parsing a DOM, plus we don't need another library.
And because the data is always in the same place and same format... It's not as if we're trying to naturally understand HTML here. Just extract strings.
I'm sorry. */
Regex confIDRegex = new Regex("data-confid=\"(\\d+)\"");
Regex confKeyRegex = new Regex("data-key=\"(\\d+)\"");
Regex confDescRegex = new Regex("<div>((Confirm|Trade with|Sell -) .+)</div>");
if (response == null || !(confIDRegex.IsMatch(response) && confKeyRegex.IsMatch(response) && confDescRegex.IsMatch(response)))
{
if (response == null || !response.Contains("<div>Nothing to confirm</div>"))
{
throw new WGTokenInvalidException();
}
return new Confirmation[0];
}
MatchCollection confIDs = confIDRegex.Matches(response);
MatchCollection confKeys = confKeyRegex.Matches(response);
MatchCollection confDescs = confDescRegex.Matches(response);
List<Confirmation> ret = new List<Confirmation>();
for (int i = 0; i < confIDs.Count; i++)
{
string confID = confIDs[i].Groups[1].Value;
string confKey = confKeys[i].Groups[1].Value;
string confDesc = confDescs[i].Groups[1].Value;
Confirmation conf = new Confirmation()
{
ConfirmationDescription = confDesc,
ConfirmationID = confID,
ConfirmationKey = confKey
};
ret.Add(conf);
}
return ret.ToArray();
}
public bool AcceptConfirmation(Confirmation conf)
{
return _sendConfirmationAjax(conf, "allow");
@@ -213,6 +264,38 @@ namespace SteamAuth
}
}
/// <summary>
/// Refreshes the Steam session. Necessary to perform confirmations if your session has expired or changed.
/// </summary>
/// <returns></returns>
public async Task<bool> RefreshSessionAsync()
{
string url = APIEndpoints.MOBILEAUTH_GETWGTOKEN;
NameValueCollection postData = new NameValueCollection();
postData.Add("access_token", this.Session.OAuthToken);
string response = await SteamWeb.RequestAsync(url, "POST", postData);
if (response == null) return false;
try
{
var refreshResponse = JsonConvert.DeserializeObject<RefreshSessionDataResponse>(response);
if (refreshResponse == null || refreshResponse.Response == null || String.IsNullOrEmpty(refreshResponse.Response.Token))
return false;
string token = this.Session.SteamID + "%7C%7C" + refreshResponse.Response.Token;
string tokenSecure = this.Session.SteamID + "%7C%7C" + refreshResponse.Response.TokenSecure;
this.Session.SteamLogin = token;
this.Session.SteamLoginSecure = tokenSecure;
return true;
}
catch (Exception e)
{
return false;
}
}
private bool _sendConfirmationAjax(Confirmation conf, string op)
{
string url = APIEndpoints.COMMUNITY_BASE + "/mobileconf/ajaxop";

View File

@@ -2,6 +2,7 @@
using System.Collections.Specialized;
using System.IO;
using System.Net;
using System.Threading.Tasks;
namespace SteamAuth
{
@@ -67,12 +68,67 @@ namespace SteamAuth
using (StreamReader responseStream = new StreamReader(response.GetResponseStream()))
{
string responseData = responseStream.ReadToEnd();
return responseData;
}
}
}
catch (WebException ex)
catch (WebException)
{
return null;
}
}
public static async Task<string> RequestAsync(string url, string method, NameValueCollection data = null, CookieContainer cookies = null, NameValueCollection headers = null, string referer = APIEndpoints.COMMUNITY_BASE)
{
string query = (data == null ? string.Empty : string.Join("&", Array.ConvertAll(data.AllKeys, key => String.Format("{0}={1}", WebUtility.UrlEncode(key), WebUtility.UrlEncode(data[key])))));
if (method == "GET")
{
url += (url.Contains("?") ? "&" : "?") + query;
}
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Method = method;
request.Accept = "text/javascript, text/html, application/xml, text/xml, */*";
request.UserAgent = "Mozilla/5.0 (Linux; U; Android 4.1.1; en-us; Google Nexus 4 - 4.1.1 - API 16 - 768x1280 Build/JRO03S) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30";
request.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip;
request.Referer = referer;
if (headers != null)
{
request.Headers.Add(headers);
}
if (cookies != null)
{
request.CookieContainer = cookies;
}
if (method == "POST")
{
request.ContentType = "application/x-www-form-urlencoded; charset=UTF-8";
request.ContentLength = query.Length;
StreamWriter requestStream = new StreamWriter(request.GetRequestStream());
requestStream.Write(query);
requestStream.Close();
}
try
{
HttpWebResponse response = (HttpWebResponse) await request.GetResponseAsync();
if (response.StatusCode != HttpStatusCode.OK)
{
return null;
}
using (StreamReader responseStream = new StreamReader(response.GetResponseStream()))
{
string responseData = responseStream.ReadToEnd();
return responseData;
}
}
catch (WebException)
{
return null;
}

View File

@@ -1,4 +1,6 @@
using System.Net;
using System;
using System.Threading.Tasks;
using System.Net;
using Newtonsoft.Json;
namespace SteamAuth
@@ -21,6 +23,15 @@ namespace SteamAuth
return Util.GetSystemUnixTime() + _timeDifference;
}
public static async Task<long> GetSteamTimeAsync()
{
if (!TimeAligner._aligned)
{
await TimeAligner.AlignTimeAsync();
}
return Util.GetSystemUnixTime() + _timeDifference;
}
public static void AlignTime()
{
long currentTime = Util.GetSystemUnixTime();
@@ -33,13 +44,30 @@ namespace SteamAuth
TimeAligner._timeDifference = (int)(query.Response.ServerTime - currentTime);
TimeAligner._aligned = true;
}
catch (WebException e)
catch (WebException)
{
return;
}
}
}
public static async Task AlignTimeAsync()
{
long currentTime = Util.GetSystemUnixTime();
WebClient client = new WebClient();
try
{
string response = await client.UploadStringTaskAsync(new Uri(APIEndpoints.TWO_FACTOR_TIME_QUERY), "steamid=0");
TimeQuery query = JsonConvert.DeserializeObject<TimeQuery>(response);
TimeAligner._timeDifference = (int)(query.Response.ServerTime - currentTime);
TimeAligner._aligned = true;
}
catch (WebException)
{
return;
}
}
internal class TimeQuery
{
[JsonProperty("response")]