Compare commits

..

32 Commits

Author SHA1 Message Date
JustArchi
90ade53ae7 Compilation fix 2016-06-24 22:27:49 +02:00
JustArchi
9a51386b7e Bump 2016-06-24 22:27:22 +02:00
JustArchi
03ee96057f Add GiftsLimiterDelay 2016-06-24 22:26:52 +02:00
JustArchi
a23bca7960 Actually handle gift only one time regardless 2016-06-24 20:54:21 +02:00
JustArchi
6b4ae6a4d7 Do not attempt to handle the same gift more than one time 2016-06-24 20:27:23 +02:00
JustArchi
5c80fd158d Misc 2016-06-24 19:26:21 +02:00
JustArchi
885800c539 Closes #263 2016-06-24 16:58:49 +02:00
JustArchi
920d4b9ed6 Misc 2016-06-24 05:41:32 +02:00
JustArchi
2d02bd609e Add CONTRIBUTING.md 2016-06-24 05:39:32 +02:00
JustArchi
e658ae33b1 Misc 2016-06-24 03:17:11 +02:00
JustArchi
379018866b Bump 2016-06-24 02:40:44 +02:00
JustArchi
1ad1e8b792 Use better format for API 2016-06-24 01:55:39 +02:00
JustArchi
c3dde4c822 Final fix 2016-06-24 01:44:58 +02:00
JustArchi
b40dc2e572 Calculate proper dupe values for the same cards 2016-06-24 01:27:11 +02:00
JustArchi
70bdd34d66 Increase default LoginLimiterDelay to 10
Seems to fix semi-rare 429 rate-limiting errors. Maybe GabeN increased limits?
2016-06-23 16:00:15 +02:00
JustArchi
56a6e10189 Correct retry logic of API requests
Timeout will result in WebClient does not support concurrent I/O operations for concurrent calls
Therefore, retry with new WebClient instead
2016-06-22 02:02:43 +02:00
JustArchi
41f8a0a474 Add !api 2016-06-20 21:20:38 +02:00
JustArchi
92f347e28b Bump 2016-06-20 20:47:09 +02:00
JustArchi
1a1914540c Fix !2faok with many confirmations 2016-06-20 20:29:12 +02:00
JustArchi
449e4f9511 Bump 2016-06-20 17:04:07 +02:00
JustArchi
959bf98039 Divide !pause into !pause and !resume 2016-06-20 17:03:55 +02:00
JustArchi
017c5eb7ef Fix misc issue with steam gifts in trades 2016-06-20 15:02:26 +02:00
JustArchi
9e575584a8 Bump 2016-06-20 14:41:14 +02:00
JustArchi
19da8c6d11 Try to solve language problem in non-invasive way 2016-06-20 14:40:14 +02:00
JustArchi
3d19a69c60 Let's hope this is the last one 2016-06-20 13:51:17 +02:00
JustArchi
f6a8d16c85 More fixes 2016-06-20 13:45:12 +02:00
JustArchi
f13991c2da Bump... 2016-06-20 13:26:43 +02:00
JustArchi
40a3d6558d GabeN broke more than I thought 2016-06-20 13:26:30 +02:00
JustArchi
fea76a3dda Derp 2016-06-20 13:18:06 +02:00
JustArchi
1087c01a2c And don't you dare to break again 2016-06-20 13:15:35 +02:00
JustArchi
fd49ff5483 Thanks GabeN 2016-06-20 13:09:27 +02:00
JustArchi
0f5d9a665c Bump 2016-06-20 13:01:42 +02:00
13 changed files with 218 additions and 79 deletions

View File

@@ -47,6 +47,8 @@ namespace ArchiSteamFarm {
private readonly SemaphoreSlim SessionSemaphore = new SemaphoreSlim(1);
private readonly WebBrowser WebBrowser;
internal bool Ready { get; private set; }
private ulong SteamID;
private DateTime LastSessionRefreshCheck = DateTime.MinValue;
@@ -67,12 +69,7 @@ namespace ArchiSteamFarm {
}
uint appID;
if (uint.TryParse(hashName.Substring(0, index), out appID)) {
return appID;
}
Logging.LogNullError(nameof(appID));
return 0;
return uint.TryParse(hashName.Substring(0, index), out appID) ? appID : 0;
}
private static Steam.Item.EType GetItemType(string name) {
@@ -117,7 +114,9 @@ namespace ArchiSteamFarm {
WebBrowser = new WebBrowser(bot.BotName);
}
internal bool Init(SteamClient steamClient, string webAPIUserNonce, string parentalPin) {
internal void OnDisconnected() => Ready = false;
internal async Task<bool> Init(SteamClient steamClient, string webAPIUserNonce, string parentalPin) {
if ((steamClient == null) || string.IsNullOrEmpty(webAPIUserNonce)) {
Logging.LogNullError(nameof(steamClient) + " || " + nameof(webAPIUserNonce), Bot.BotName);
return false;
@@ -179,10 +178,12 @@ namespace ArchiSteamFarm {
string steamLoginSecure = authResult["tokensecure"].Value;
WebBrowser.CookieContainer.Add(new Cookie("steamLoginSecure", steamLoginSecure, "/", "." + SteamCommunityHost));
if (!UnlockParentalAccount(parentalPin).Result) {
// Unlock Steam Parental if needed
if (!await UnlockParentalAccount(parentalPin).ConfigureAwait(false)) {
return false;
}
Ready = true;
LastSessionRefreshCheck = DateTime.Now;
return true;
}
@@ -246,7 +247,7 @@ namespace ArchiSteamFarm {
return null;
}
string request = SteamCommunityURL + "/mobileconf/conf?p=" + deviceID + "&a=" + SteamID + "&k=" + WebUtility.UrlEncode(confirmationHash) + "&t=" + time + "&m=android&tag=conf";
string request = SteamCommunityURL + "/mobileconf/conf?l=english&p=" + deviceID + "&a=" + SteamID + "&k=" + WebUtility.UrlEncode(confirmationHash) + "&t=" + time + "&m=android&tag=conf";
return await WebBrowser.UrlGetToHtmlDocumentRetry(request).ConfigureAwait(false);
}
@@ -369,10 +370,10 @@ namespace ArchiSteamFarm {
}
KeyValue response = null;
for (byte i = 0; (i < WebBrowser.MaxRetries) && (response == null); i++) {
using (dynamic iPlayerService = WebAPI.GetInterface("IPlayerService", Bot.BotConfig.SteamApiKey)) {
iPlayerService.Timeout = Timeout;
for (byte i = 0; (i < WebBrowser.MaxRetries) && (response == null); i++) {
try {
response = iPlayerService.GetOwnedGames(
steamid: steamID,
@@ -406,10 +407,10 @@ namespace ArchiSteamFarm {
internal uint GetServerTime() {
KeyValue response = null;
for (byte i = 0; (i < WebBrowser.MaxRetries) && (response == null); i++) {
using (dynamic iTwoFactorService = WebAPI.GetInterface("ITwoFactorService")) {
iTwoFactorService.Timeout = Timeout;
for (byte i = 0; (i < WebBrowser.MaxRetries) && (response == null); i++) {
try {
response = iTwoFactorService.QueryTime(
method: WebRequestMethods.Http.Post,
@@ -435,7 +436,7 @@ namespace ArchiSteamFarm {
return null;
}
string request = SteamCommunityURL + "/tradeoffer/" + tradeID;
string request = SteamCommunityURL + "/tradeoffer/" + tradeID + "?l=english";
HtmlDocument htmlDocument = await WebBrowser.UrlGetToHtmlDocumentRetry(request).ConfigureAwait(false);
if (htmlDocument == null) {
@@ -487,10 +488,10 @@ namespace ArchiSteamFarm {
}
KeyValue response = null;
for (byte i = 0; (i < WebBrowser.MaxRetries) && (response == null); i++) {
using (dynamic iEconService = WebAPI.GetInterface("IEconService", Bot.BotConfig.SteamApiKey)) {
iEconService.Timeout = Timeout;
for (byte i = 0; (i < WebBrowser.MaxRetries) && (response == null); i++) {
try {
response = iEconService.GetTradeOffers(
get_received_offers: 1,
@@ -624,10 +625,10 @@ namespace ArchiSteamFarm {
}
KeyValue response = null;
for (byte i = 0; (i < WebBrowser.MaxRetries) && (response == null); i++) {
using (dynamic iEconService = WebAPI.GetInterface("IEconService", Bot.BotConfig.SteamApiKey)) {
iEconService.Timeout = Timeout;
for (byte i = 0; (i < WebBrowser.MaxRetries) && (response == null); i++) {
try {
response = iEconService.DeclineTradeOffer(
tradeofferid: tradeID.ToString(),
@@ -837,7 +838,7 @@ namespace ArchiSteamFarm {
return null;
}
string request = SteamCommunityURL + "/my/badges?p=" + page;
string request = SteamCommunityURL + "/my/badges?l=english&p=" + page;
return await WebBrowser.UrlGetToHtmlDocumentRetry(request).ConfigureAwait(false);
}
@@ -852,7 +853,7 @@ namespace ArchiSteamFarm {
return null;
}
string request = SteamCommunityURL + "/my/gamecards/" + appID;
string request = SteamCommunityURL + "/my/gamecards/" + appID + "?l=english";
return await WebBrowser.UrlGetToHtmlDocumentRetry(request).ConfigureAwait(false);
}

View File

@@ -45,6 +45,7 @@ namespace ArchiSteamFarm {
internal static readonly Dictionary<string, Bot> Bots = new Dictionary<string, Bot>();
private static readonly uint LoginID = MsgClientLogon.ObfuscationMask; // This must be the same for all ASF bots and all ASF processes
private static readonly SemaphoreSlim GiftsSemaphore = new SemaphoreSlim(1);
private static readonly SemaphoreSlim LoginSemaphore = new SemaphoreSlim(1);
internal readonly string BotName;
@@ -56,14 +57,20 @@ namespace ArchiSteamFarm {
private readonly string SentryFile;
private readonly BotDatabase BotDatabase;
private readonly CallbackManager CallbackManager;
[JsonProperty]
private readonly CardsFarmer CardsFarmer;
private readonly ConcurrentHashSet<ulong> HandledGifts = new ConcurrentHashSet<ulong>();
private readonly SteamApps SteamApps;
private readonly SteamFriends SteamFriends;
private readonly SteamUser SteamUser;
private readonly Timer AcceptConfirmationsTimer, SendItemsTimer;
private readonly Trading Trading;
[JsonProperty]
internal bool KeepRunning { get; private set; }
internal bool PlayingBlocked { get; private set; }
private bool FirstTradeSent, InvalidPassword, SkipFirstShutdown;
@@ -78,7 +85,7 @@ namespace ArchiSteamFarm {
initialized = true;
} catch (Exception e) {
Logging.LogGenericException(e);
await Utilities.SleepAsync(1000).ConfigureAwait(false);
await Task.Delay(1000).ConfigureAwait(false);
}
}
@@ -107,10 +114,18 @@ namespace ArchiSteamFarm {
return false;
}
private static async Task LimitGiftsRequestsAsync() {
await GiftsSemaphore.WaitAsync().ConfigureAwait(false);
Task.Run(async () => {
await Task.Delay(Program.GlobalConfig.GiftsLimiterDelay * 1000).ConfigureAwait(false);
GiftsSemaphore.Release();
}).Forget();
}
private static async Task LimitLoginRequestsAsync() {
await LoginSemaphore.WaitAsync().ConfigureAwait(false);
Task.Run(async () => {
await Utilities.SleepAsync(Program.GlobalConfig.LoginLimiterDelay * 1000).ConfigureAwait(false);
await Task.Delay(Program.GlobalConfig.LoginLimiterDelay * 1000).ConfigureAwait(false);
LoginSemaphore.Release();
}).Forget();
}
@@ -226,7 +241,7 @@ namespace ArchiSteamFarm {
if ((SendItemsTimer == null) && (BotConfig.SendTradePeriod > 0)) {
SendItemsTimer = new Timer(
async e => await ResponseSendTrade(BotConfig.SteamMasterID).ConfigureAwait(false),
async e => await ResponseLoot(BotConfig.SteamMasterID).ConfigureAwait(false),
null,
TimeSpan.FromHours(BotConfig.SendTradePeriod), // Delay
TimeSpan.FromHours(BotConfig.SendTradePeriod) // Period
@@ -252,7 +267,7 @@ namespace ArchiSteamFarm {
}
if (acceptedType != Steam.ConfirmationDetails.EType.Unknown) {
if (confirmations.RemoveWhere(confirmation => confirmation.Type != acceptedType) > 0) {
if (confirmations.RemoveWhere(confirmation => (confirmation.Type != acceptedType) && (confirmation.Type != Steam.ConfirmationDetails.EType.Other)) > 0) {
confirmations.TrimExcess();
if (confirmations.Count == 0) {
return;
@@ -261,8 +276,8 @@ namespace ArchiSteamFarm {
}
if ((acceptedSteamID != 0) || ((acceptedTradeIDs != null) && (acceptedTradeIDs.Count > 0))) {
List<Task<Steam.ConfirmationDetails>> detailsTasks = confirmations.Select(BotDatabase.MobileAuthenticator.GetConfirmationDetails).ToList();
Steam.ConfirmationDetails[] detailsResults = await Task.WhenAll(detailsTasks).ConfigureAwait(false);
List<Task<Steam.ConfirmationDetails>> tasks = confirmations.Select(BotDatabase.MobileAuthenticator.GetConfirmationDetails).ToList();
Steam.ConfirmationDetails[] detailsResults = await Task.WhenAll(tasks).ConfigureAwait(false);
HashSet<uint> ignoredConfirmationIDs = new HashSet<uint>();
foreach (Steam.ConfirmationDetails details in detailsResults.Where(details => (details != null) && (
@@ -282,8 +297,11 @@ namespace ArchiSteamFarm {
}
}
List<Task<bool>> tasks = confirmations.Select(confirmation => BotDatabase.MobileAuthenticator.HandleConfirmation(confirmation, accept)).ToList();
await Task.WhenAll(tasks).ConfigureAwait(false);
// This could be done in parallel, but for some reason Steam allows only one confirmation being accepted at the time
// Therefore, even though no physical barrier stops us from doing so, we execute this synchronously
foreach (MobileAuthenticator.Confirmation confirmation in confirmations) {
await BotDatabase.MobileAuthenticator.HandleConfirmation(confirmation, accept).ConfigureAwait(false);
}
}
internal async Task<bool> RefreshSession() {
@@ -306,7 +324,7 @@ namespace ArchiSteamFarm {
return false;
}
if (ArchiWebHandler.Init(SteamClient, callback.Nonce, BotConfig.SteamParentalPIN)) {
if (await ArchiWebHandler.Init(SteamClient, callback.Nonce, BotConfig.SteamParentalPIN).ConfigureAwait(false)) {
return true;
}
@@ -321,7 +339,7 @@ namespace ArchiSteamFarm {
if ((farmedSomething || !FirstTradeSent) && BotConfig.SendOnFarmingFinished) {
FirstTradeSent = true;
await ResponseSendTrade(BotConfig.SteamMasterID).ConfigureAwait(false);
await ResponseLoot(BotConfig.SteamMasterID).ConfigureAwait(false);
}
if (BotConfig.ShutdownOnFarmingFinished) {
@@ -351,6 +369,8 @@ namespace ArchiSteamFarm {
return await Response2FAConfirm(steamID, false).ConfigureAwait(false);
case "!2faok":
return await Response2FAConfirm(steamID, true).ConfigureAwait(false);
case "!api":
return ResponseAPI(steamID);
case "!exit":
return ResponseExit(steamID);
case "!farm":
@@ -358,11 +378,15 @@ namespace ArchiSteamFarm {
case "!help":
return ResponseHelp(steamID);
case "!loot":
return await ResponseSendTrade(steamID).ConfigureAwait(false);
return await ResponseLoot(steamID).ConfigureAwait(false);
case "!lootall":
return await ResponseLootAll(steamID).ConfigureAwait(false);
case "!pause":
return await ResponsePause(steamID).ConfigureAwait(false);
return await ResponsePause(steamID, true).ConfigureAwait(false);
case "!rejoinchat":
return ResponseRejoinChat(steamID);
case "!resume":
return await ResponsePause(steamID, false).ConfigureAwait(false);
case "!restart":
return ResponseRestart(steamID);
case "!status":
@@ -397,7 +421,7 @@ namespace ArchiSteamFarm {
case "!farm":
return await ResponseFarm(steamID, args[1]).ConfigureAwait(false);
case "!loot":
return await ResponseSendTrade(steamID, args[1]).ConfigureAwait(false);
return await ResponseLoot(steamID, args[1]).ConfigureAwait(false);
case "!owns":
if (args.Length > 2) {
return await ResponseOwns(steamID, args[1], args[2]).ConfigureAwait(false);
@@ -405,7 +429,7 @@ namespace ArchiSteamFarm {
return await ResponseOwns(steamID, BotName, args[1]).ConfigureAwait(false);
case "!pause":
return await ResponsePause(steamID, args[1]).ConfigureAwait(false);
return await ResponsePause(steamID, args[1], true).ConfigureAwait(false);
case "!play":
if (args.Length > 2) {
return await ResponsePlay(steamID, args[1], args[2]).ConfigureAwait(false);
@@ -418,6 +442,8 @@ namespace ArchiSteamFarm {
}
return await ResponseRedeem(steamID, BotName, args[1], false).ConfigureAwait(false);
case "!resume":
return await ResponsePause(steamID, args[1], false).ConfigureAwait(false);
case "!start":
return await ResponseStart(steamID, args[1]).ConfigureAwait(false);
case "!status":
@@ -500,7 +526,7 @@ namespace ArchiSteamFarm {
Logging.LogGenericInfo("Successfully finished importing mobile authenticator!", BotName);
}
private async Task<string> ResponsePause(ulong steamID) {
private async Task<string> ResponsePause(ulong steamID, bool pause) {
if (steamID == 0) {
Logging.LogNullError(nameof(steamID), BotName);
return null;
@@ -510,16 +536,24 @@ namespace ArchiSteamFarm {
return null;
}
if (pause) {
if (CardsFarmer.ManualMode) {
await CardsFarmer.SwitchToManualMode(false).ConfigureAwait(false);
return "Automatic farming is enabled again!";
return "Automatic farming is stopped already!";
}
await CardsFarmer.SwitchToManualMode(true).ConfigureAwait(false);
return "Automatic farming is now stopped!";
}
private static async Task<string> ResponsePause(ulong steamID, string botName) {
if (!CardsFarmer.ManualMode) {
return "Automatic farming is enabled already!";
}
await CardsFarmer.SwitchToManualMode(false).ConfigureAwait(false);
return "Automatic farming is now enabled!";
}
private static async Task<string> ResponsePause(ulong steamID, string botName, bool pause) {
if ((steamID == 0) || string.IsNullOrEmpty(botName)) {
Logging.LogNullError(nameof(steamID) + " || " + nameof(botName));
return null;
@@ -527,7 +561,7 @@ namespace ArchiSteamFarm {
Bot bot;
if (Bots.TryGetValue(botName, out bot)) {
return await bot.ResponsePause(steamID).ConfigureAwait(false);
return await bot.ResponsePause(steamID, pause).ConfigureAwait(false);
}
if (IsOwner(steamID)) {
@@ -608,7 +642,7 @@ namespace ArchiSteamFarm {
return result.ToString();
}
private async Task<string> ResponseSendTrade(ulong steamID) {
private async Task<string> ResponseLoot(ulong steamID) {
if (steamID == 0) {
Logging.LogNullError(nameof(steamID), BotName);
return null;
@@ -649,7 +683,7 @@ namespace ArchiSteamFarm {
return "Trade offer sent successfully!";
}
private static async Task<string> ResponseSendTrade(ulong steamID, string botName) {
private static async Task<string> ResponseLoot(ulong steamID, string botName) {
if ((steamID == 0) || string.IsNullOrEmpty(botName)) {
Logging.LogNullError(nameof(steamID) + " || " + nameof(botName));
return null;
@@ -657,7 +691,7 @@ namespace ArchiSteamFarm {
Bot bot;
if (Bots.TryGetValue(botName, out bot)) {
return await bot.ResponseSendTrade(steamID).ConfigureAwait(false);
return await bot.ResponseLoot(steamID).ConfigureAwait(false);
}
if (IsOwner(steamID)) {
@@ -667,6 +701,20 @@ namespace ArchiSteamFarm {
return null;
}
private static async Task<string> ResponseLootAll(ulong steamID) {
if (steamID == 0) {
Logging.LogNullError(nameof(steamID));
return null;
}
if (!IsOwner(steamID)) {
return null;
}
await Task.WhenAll(Bots.Values.Select(bot => bot.ResponseLoot(steamID))).ConfigureAwait(false);
return "Done!";
}
private async Task<string> Response2FA(ulong steamID) {
if (steamID == 0) {
Logging.LogNullError(nameof(steamID), BotName);
@@ -739,6 +787,28 @@ namespace ArchiSteamFarm {
return null;
}
private static string ResponseAPI(ulong steamID) {
if (steamID == 0) {
Logging.LogNullError(nameof(steamID));
return null;
}
if (!IsOwner(steamID)) {
return null;
}
var response = new {
Bots
};
try {
return JsonConvert.SerializeObject(response);
} catch (JsonException e) {
Logging.LogGenericException(e);
return null;
}
}
private static string ResponseExit(ulong steamID) {
if (steamID == 0) {
Logging.LogNullError(nameof(steamID));
@@ -751,7 +821,7 @@ namespace ArchiSteamFarm {
// Schedule the task after some time so user can receive response
Task.Run(async () => {
await Utilities.SleepAsync(1000).ConfigureAwait(false);
await Task.Delay(1000).ConfigureAwait(false);
Program.Exit();
}).Forget();
@@ -953,7 +1023,7 @@ namespace ArchiSteamFarm {
// Schedule the task after some time so user can receive response
Task.Run(async () => {
await Utilities.SleepAsync(1000).ConfigureAwait(false);
await Task.Delay(1000).ConfigureAwait(false);
Program.Restart();
}).Forget();
@@ -1452,10 +1522,12 @@ namespace ArchiSteamFarm {
return;
}
ArchiWebHandler.OnDisconnected();
Logging.LogGenericInfo("Disconnected from Steam!", BotName);
FirstTradeSent = false;
CardsFarmer.StopFarming().Forget();
HandledGifts.ClearAndTrim();
// If we initiated disconnect, do not attempt to reconnect
if (callback.UserInitiated) {
@@ -1469,7 +1541,7 @@ namespace ArchiSteamFarm {
Logging.LogGenericInfo("Removed expired login key", BotName);
} else { // If we didn't use login key, InvalidPassword usually means we got captcha or other network-based throttling
Logging.LogGenericInfo("Will retry after 25 minutes...", BotName);
await Utilities.SleepAsync(25 * 60 * 1000).ConfigureAwait(false); // Captcha disappears after around 20 minutes, so we make it 25
await Task.Delay(25 * 60 * 1000).ConfigureAwait(false); // Captcha disappears after around 20 minutes, so we make it 25
}
}
@@ -1503,9 +1575,21 @@ namespace ArchiSteamFarm {
return;
}
for (byte i = 0; (i < Program.GlobalConfig.HttpTimeout) && !ArchiWebHandler.Ready; i++) {
await Task.Delay(1000).ConfigureAwait(false);
}
if (!ArchiWebHandler.Ready) {
return;
}
bool acceptedSomething = false;
foreach (ulong gid in callback.GuestPasses.Select(guestPass => guestPass["gid"].AsUnsignedLong()).Where(gid => gid != 0)) {
foreach (ulong gid in callback.GuestPasses.Select(guestPass => guestPass["gid"].AsUnsignedLong()).Where(gid => (gid != 0) && !HandledGifts.Contains(gid))) {
HandledGifts.Add(gid);
Logging.LogGenericInfo("Accepting gift: " + gid + "...", BotName);
await LimitGiftsRequestsAsync().ConfigureAwait(false);
if (await ArchiWebHandler.AcceptGift(gid).ConfigureAwait(false)) {
acceptedSomething = true;
Logging.LogGenericInfo("Success!", BotName);
@@ -1698,7 +1782,7 @@ namespace ArchiSteamFarm {
}
}
if (!ArchiWebHandler.Init(SteamClient, callback.WebAPIUserNonce, BotConfig.SteamParentalPIN)) {
if (!await ArchiWebHandler.Init(SteamClient, callback.WebAPIUserNonce, BotConfig.SteamParentalPIN).ConfigureAwait(false)) {
if (!await RefreshSession().ConfigureAwait(false)) {
return;
}
@@ -1724,7 +1808,7 @@ namespace ArchiSteamFarm {
Trading.CheckTrades().Forget();
await Utilities.SleepAsync(1000).ConfigureAwait(false); // Wait a second for eventual PlayingSessionStateCallback
await Task.Delay(1000).ConfigureAwait(false); // Wait a second for eventual PlayingSessionStateCallback
CardsFarmer.StartFarming().Forget();
break;
case EResult.NoConnection:

View File

@@ -31,10 +31,14 @@ using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace ArchiSteamFarm {
internal sealed class CardsFarmer {
[JsonProperty]
internal readonly ConcurrentDictionary<uint, float> GamesToFarm = new ConcurrentDictionary<uint, float>();
[JsonProperty]
internal readonly ConcurrentHashSet<uint> CurrentGamesFarming = new ConcurrentHashSet<uint>();
private readonly ManualResetEventSlim FarmResetEvent = new ManualResetEventSlim(false);
@@ -42,6 +46,7 @@ namespace ArchiSteamFarm {
private readonly Bot Bot;
private readonly Timer Timer;
[JsonProperty]
internal bool ManualMode { get; private set; }
private bool KeepFarming, NowFarming;
@@ -175,7 +180,7 @@ namespace ArchiSteamFarm {
Logging.LogGenericInfo("Waiting for reaction...", Bot.BotName);
for (byte i = 0; (i < 5) && NowFarming; i++) {
await Utilities.SleepAsync(1000).ConfigureAwait(false);
await Task.Delay(1000).ConfigureAwait(false);
}
if (NowFarming) {
@@ -388,8 +393,18 @@ namespace ArchiSteamFarm {
return null;
}
Logging.LogGenericInfo("Status for " + appID + ": " + progress, Bot.BotName);
return progress.Equals("No card drops remaining");
byte cardsRemaining = 0;
Match match = Regex.Match(progress, @"\d+");
if (match.Success) {
if (!byte.TryParse(match.Value, out cardsRemaining)) {
Logging.LogNullError(nameof(cardsRemaining), Bot.BotName);
return null;
}
}
Logging.LogGenericInfo("Status for " + appID + ": " + cardsRemaining + " cards remaining", Bot.BotName);
return cardsRemaining > 0;
}
private bool FarmMultiple() {

View File

@@ -81,11 +81,14 @@ namespace ArchiSteamFarm {
internal byte FarmingDelay { get; private set; } = DefaultFarmingDelay;
[JsonProperty(Required = Required.DisallowNull)]
internal byte LoginLimiterDelay { get; private set; } = 7;
internal byte LoginLimiterDelay { get; private set; } = 10;
[JsonProperty(Required = Required.DisallowNull)]
internal byte InventoryLimiterDelay { get; private set; } = 3;
[JsonProperty(Required = Required.DisallowNull)]
internal byte GiftsLimiterDelay { get; private set; } = 1;
[JsonProperty(Required = Required.DisallowNull)]
internal bool ForceHttp { get; private set; } = false;

View File

@@ -442,8 +442,10 @@ namespace ArchiSteamFarm.JSON {
}
}
#pragma warning disable 649
[JsonProperty(PropertyName = "html")]
private string HTML;
#pragma warning restore 649
private uint _OtherSteamID3;
private uint OtherSteamID3 {

View File

@@ -91,7 +91,7 @@ namespace ArchiSteamFarm {
// We booted successfully so we can now remove old exe file
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);
await Task.Delay(1000).ConfigureAwait(false);
try {
File.Delete(oldExeFile);
@@ -172,7 +172,7 @@ namespace ArchiSteamFarm {
if (!updateOverride && !GlobalConfig.AutoUpdates) {
Logging.LogGenericInfo("New version is available!");
Logging.LogGenericInfo("Consider updating yourself!");
await Utilities.SleepAsync(5000).ConfigureAwait(false);
await Task.Delay(5000).ConfigureAwait(false);
return;
}
@@ -199,6 +199,7 @@ namespace ArchiSteamFarm {
return;
}
Logging.LogGenericInfo("Downloading new version...");
byte[] result = await WebBrowser.UrlGetToBytesRetry(binaryAsset.DownloadURL).ConfigureAwait(false);
if (result == null) {
return;
@@ -247,11 +248,11 @@ namespace ArchiSteamFarm {
if (GlobalConfig.AutoRestart) {
Logging.LogGenericInfo("Restarting...");
await Utilities.SleepAsync(5000).ConfigureAwait(false);
await Task.Delay(5000).ConfigureAwait(false);
Restart();
} else {
Logging.LogGenericInfo("Exiting...");
await Utilities.SleepAsync(5000).ConfigureAwait(false);
await Task.Delay(5000).ConfigureAwait(false);
Exit();
}
}

View File

@@ -31,5 +31,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("2.1.0.2")]
[assembly: AssemblyFileVersion("2.1.0.2")]
[assembly: AssemblyVersion("2.1.0.9")]
[assembly: AssemblyFileVersion("2.1.0.9")]

View File

@@ -44,7 +44,7 @@ namespace ArchiSteamFarm {
internal static async Task LimitInventoryRequestsAsync() {
await InventorySemaphore.WaitAsync().ConfigureAwait(false);
Task.Run(async () => {
await Utilities.SleepAsync(Program.GlobalConfig.InventoryLimiterDelay * 1000).ConfigureAwait(false);
await Task.Delay(Program.GlobalConfig.InventoryLimiterDelay * 1000).ConfigureAwait(false);
InventorySemaphore.Release();
}).Forget();
}
@@ -201,14 +201,16 @@ namespace ArchiSteamFarm {
// Calculate our value of items to give
List<uint> amountsToGive = new List<uint>(tradeOffer.ItemsToGive.Count);
Dictionary<ulong, uint> amountMapToGive = new Dictionary<ulong, uint>(amountMap);
foreach (ulong key in tradeOffer.ItemsToGive.Select(item => item.ClassID)) {
uint amount;
if (!amountMap.TryGetValue(key, out amount)) {
if (!amountMapToGive.TryGetValue(key, out amount)) {
amountsToGive.Add(0);
continue;
}
amountsToGive.Add(amount);
amountMapToGive[key] = amount - 1; // We're giving one, so we have one less
}
// Sort it ascending
@@ -216,20 +218,23 @@ namespace ArchiSteamFarm {
// Calculate our value of items to receive
List<uint> amountsToReceive = new List<uint>(tradeOffer.ItemsToReceive.Count);
Dictionary<ulong, uint> amountMapToReceive = new Dictionary<ulong, uint>(amountMap);
foreach (ulong key in tradeOffer.ItemsToReceive.Select(item => item.ClassID)) {
uint amount;
if (!amountMap.TryGetValue(key, out amount)) {
if (!amountMapToReceive.TryGetValue(key, out amount)) {
amountsToReceive.Add(0);
continue;
}
amountsToReceive.Add(amount);
amountMapToReceive[key] = amount + 1; // We're getting one, so we have one more
}
// Sort it ascending
amountsToReceive.Sort();
// Check actual difference
// We sum only values at proper indexes of giving, because user might be overpaying
int difference = amountsToGive.Select((t, i) => (int) (t - amountsToReceive[i])).Sum();
// Trade is worth for us if the difference is greater than 0

View File

@@ -53,14 +53,5 @@ namespace ArchiSteamFarm {
}
internal static uint GetUnixTime() => (uint) DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalSeconds;
internal static Task SleepAsync(int miliseconds) {
if (miliseconds >= 0) {
return Task.Delay(miliseconds);
}
Logging.LogNullError(nameof(miliseconds));
return Task.FromResult(true);
}
}
}

View File

@@ -80,6 +80,9 @@ namespace ArchiSteamFarm {
// Most web services expect that UserAgent is set, so we declare it globally
HttpClient.DefaultRequestHeaders.UserAgent.ParseAdd(DefaultUserAgent);
// We should always operate in English language, declare it globally
HttpClient.DefaultRequestHeaders.AcceptLanguage.ParseAdd("en-US,en;q=0.8,en-GB;q=0.6");
}
internal async Task<bool> UrlHeadRetry(string request, string referer = null) {

View File

@@ -9,8 +9,9 @@
"MaxFarmingTime": 10,
"IdleFarmingPeriod": 3,
"FarmingDelay": 15,
"LoginLimiterDelay": 7,
"LoginLimiterDelay": 10,
"InventoryLimiterDelay": 3,
"GiftsLimiterDelay": 1,
"ForceHttp": false,
"HttpTimeout": 60,
"WCFHostname": "localhost",

30
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,30 @@
# Contributing
Before making an issue or pull request, you should carefully read **[ASF wiki](https://github.com/JustArchi/ArchiSteamFarm/wiki)** first.
## Issues
GitHub **[issues](https://github.com/JustArchi/ArchiSteamFarm/issues)** page is being used for ASF TODO list, regarding both features and bugs. It has rather strict policy - GitHub is not technical support and all cases that are not suggestions or bug reports should NOT be posted there. You have ASF chat and Steam group for general discussion, questions or technical issues. Please avoid using GitHub issues, unless you indeed want to report a bug or suggest an enhancement. Even prior to doing that, please make sure that you're indeed dealing with a bug, or your suggestion makes sense, preferably by asking on chat/steam group first. Invalid issues will be closed immediately.
---
### Bugs
Posting a log is **mandatory**, regardless if it contains information that is relevant or not. You're allowed to make small modifications such as changing bot names to something more generic, but you should not be doing anything else. You want us to fix the bug you've encountered, then help us instead of making it harder - we're not being paid for that, and we're not forced to fix the bug you've encountered. Include as much relevant info as possible - if bug is reproducable, when it happens, if it's a result of a command - which one, does it happen always or only sometimes, with one account or all of them - everything you consider appropriate, that would help us reproduce the bug and fix it. The more information you include, the higher the chance of bug getting fixed. And this is probably what you want, right?
---
### Suggestions
ASF has rather strict scope - farming Steam cards from Steam games, which means that anything going greatly out of the scope will not be accepted, even if it's considered useful. A good example of that is Steam discovery queue, that provides extra cards during Steam sales - this is out of the scope of ASF as a program, ASF focuses on one task and is doing it efficiently, if you want to create your own bot that does exactly what you want - pay somebody for creating it.
If your suggestion doesn't go out of the scope of ASF, then explain to us in the issue why you consider it useful, why do you think that adding it to ASF is beneficial for **all users**, not yourself. Why we should spend our time coding it, convince us. If suggestion indeed makes sense, or can be considered practical, most likely we won't have anything against that, but **you** should be the one pointing out advantages, not us.
---
## Pull requests
In general any pull request is welcome and should be accepted, unless there is a strong reason against it. A strong reason includes e.g. a feature going potentially out of the scope of ASF. If you're improving existing codebase, rewriting code to be more efficient, clean, better commented - there is absolutely no reason to reject it. If you want to add missing feature, and you're not sure if it should be included in ASF, it won't hurt to ask before spending your own time.
Every pull request is carefully examined by our continuous integration system - it won't be accepted if it doesn't compile properly or causes any test to fail. We also expect that you at least barely tested the modification you're trying to add, and not blindly editing the file without even checking if it compiles. Consider the fact that you're not coding it only for yourself, but for thousands of users.

View File

@@ -81,11 +81,14 @@ namespace ConfigGenerator {
public byte FarmingDelay { get; set; } = DefaultFarmingDelay;
[JsonProperty(Required = Required.DisallowNull)]
public byte LoginLimiterDelay { get; set; } = 7;
public byte LoginLimiterDelay { get; set; } = 10;
[JsonProperty(Required = Required.DisallowNull)]
public byte InventoryLimiterDelay { get; set; } = 3;
[JsonProperty(Required = Required.DisallowNull)]
public byte GiftsLimiterDelay { get; set; } = 1;
[JsonProperty(Required = Required.DisallowNull)]
public bool ForceHttp { get; set; } = false;