mirror of
https://github.com/JustArchiNET/ArchiSteamFarm.git
synced 2025-12-28 12:10:47 +00:00
Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
53dcd22cea | ||
|
|
af050ae67e | ||
|
|
914936acdc | ||
|
|
4bba55e8fd | ||
|
|
3e1358b363 | ||
|
|
fbb24506e2 | ||
|
|
48eae1be1a | ||
|
|
6bbe543fdd | ||
|
|
afe1d57605 | ||
|
|
5596e55c7b | ||
|
|
73cefc697e | ||
|
|
5680929203 | ||
|
|
c94547c68e | ||
|
|
11c748192f | ||
|
|
8cc3fec432 | ||
|
|
7baa54377a | ||
|
|
4bc7fded2d | ||
|
|
24aa1b4873 | ||
|
|
561f8f61df | ||
|
|
c33f575c40 | ||
|
|
2c54f6b051 | ||
|
|
e0385cd343 | ||
|
|
80abd3ed69 | ||
|
|
e726558e72 | ||
|
|
07af05ad00 | ||
|
|
ef5b108b34 | ||
|
|
1ee9fec845 | ||
|
|
4bf1462381 | ||
|
|
e41d2cf37e | ||
|
|
fac5e65035 | ||
|
|
b44115711b |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -23,6 +23,7 @@ out/
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
|
||||
# User-specific files
|
||||
.vs/
|
||||
*.suo
|
||||
*.user
|
||||
*.sln.docstates
|
||||
|
||||
@@ -1,8 +1,22 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/EMPTY_BLOCK_STYLE/@EntryValue">TOGETHER</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ACCESSOR_DECLARATION_BRACES/@EntryValue">END_OF_LINE</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ACCESSOR_OWNER_DECLARATION_BRACES/@EntryValue">END_OF_LINE</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ANONYMOUS_METHOD_DECLARATION_BRACES/@EntryValue">END_OF_LINE</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/CASE_BLOCK_BRACES/@EntryValue">END_OF_LINE</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/EMPTY_BLOCK_STYLE/@EntryValue">TOGETHER_SAME_LINE</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/INITIALIZER_BRACES/@EntryValue">END_OF_LINE</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/INVOCABLE_DECLARATION_BRACES/@EntryValue">END_OF_LINE</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/OTHER_BRACES/@EntryValue">END_OF_LINE</s:String>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_CATCH_ON_NEW_LINE/@EntryValue">False</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_ELSE_ON_NEW_LINE/@EntryValue">False</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_FIELD_ATTRIBUTE_ON_SAME_LINE/@EntryValue">False</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_FINALLY_ON_NEW_LINE/@EntryValue">False</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_SIMPLE_ACCESSOR_ATTRIBUTE_ON_SAME_LINE/@EntryValue">False</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_WITHING_EMPTY_BRACES/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/TYPE_DECLARATION_BRACES/@EntryValue">END_OF_LINE</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForBuiltInTypes/@EntryValue">UseExplicitType</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForOtherTypes/@EntryValue">UseExplicitType</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForSimpleTypes/@EntryValue">UseExplicitType</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=AES/@EntryIndexedValue">AES</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=API/@EntryIndexedValue">API</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=ASF/@EntryIndexedValue">ASF</s:String>
|
||||
|
||||
@@ -377,6 +377,11 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
if (response != null) {
|
||||
// TODO: Remove me
|
||||
if (!response.Success) {
|
||||
Logging.LogGenericError("HandleConfirmations() debug content: " + json, Bot.BotName);
|
||||
}
|
||||
|
||||
return response.Success;
|
||||
}
|
||||
|
||||
|
||||
@@ -253,41 +253,45 @@ namespace ArchiSteamFarm {
|
||||
Trading?.Dispose();
|
||||
}
|
||||
|
||||
internal async Task AcceptConfirmations(bool accept, Steam.ConfirmationDetails.EType acceptedType = Steam.ConfirmationDetails.EType.Unknown, ulong acceptedSteamID = 0, HashSet<ulong> acceptedTradeIDs = null) {
|
||||
internal async Task<bool> AcceptConfirmations(bool accept, Steam.ConfirmationDetails.EType acceptedType = Steam.ConfirmationDetails.EType.Unknown, ulong acceptedSteamID = 0, HashSet<ulong> acceptedTradeIDs = null) {
|
||||
if (BotDatabase.MobileAuthenticator == null) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
HashSet<MobileAuthenticator.Confirmation> confirmations = await BotDatabase.MobileAuthenticator.GetConfirmations().ConfigureAwait(false);
|
||||
if ((confirmations == null) || (confirmations.Count == 0)) {
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (acceptedType != Steam.ConfirmationDetails.EType.Unknown) {
|
||||
if (confirmations.RemoveWhere(confirmation => (confirmation.Type != acceptedType) && (confirmation.Type != Steam.ConfirmationDetails.EType.Other)) > 0) {
|
||||
if (confirmations.Count == 0) {
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((acceptedSteamID != 0) || ((acceptedTradeIDs != null) && (acceptedTradeIDs.Count > 0))) {
|
||||
Steam.ConfirmationDetails[] detailsResults = await Task.WhenAll(confirmations.Select(BotDatabase.MobileAuthenticator.GetConfirmationDetails)).ConfigureAwait(false);
|
||||
|
||||
HashSet<MobileAuthenticator.Confirmation> ignoredConfirmations = new HashSet<MobileAuthenticator.Confirmation>(detailsResults.Where(details => (details != null) && (
|
||||
((acceptedSteamID != 0) && (details.OtherSteamID64 != 0) && (acceptedSteamID != details.OtherSteamID64)) ||
|
||||
((acceptedTradeIDs != null) && (details.TradeOfferID != 0) && !acceptedTradeIDs.Contains(details.TradeOfferID))
|
||||
)).Select(details => details.Confirmation));
|
||||
|
||||
if (ignoredConfirmations.Count > 0) {
|
||||
confirmations.ExceptWith(ignoredConfirmations);
|
||||
if (confirmations.Count == 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if ((acceptedSteamID == 0) && ((acceptedTradeIDs == null) || (acceptedTradeIDs.Count == 0))) {
|
||||
return await BotDatabase.MobileAuthenticator.HandleConfirmations(confirmations, accept).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await BotDatabase.MobileAuthenticator.HandleConfirmations(confirmations, accept).ConfigureAwait(false);
|
||||
Steam.ConfirmationDetails[] detailsResults = await Task.WhenAll(confirmations.Select(BotDatabase.MobileAuthenticator.GetConfirmationDetails)).ConfigureAwait(false);
|
||||
|
||||
HashSet<MobileAuthenticator.Confirmation> ignoredConfirmations = new HashSet<MobileAuthenticator.Confirmation>(detailsResults.Where(details => (details != null) && (
|
||||
((acceptedSteamID != 0) && (details.OtherSteamID64 != 0) && (acceptedSteamID != details.OtherSteamID64)) ||
|
||||
((acceptedTradeIDs != null) && (details.TradeOfferID != 0) && !acceptedTradeIDs.Contains(details.TradeOfferID))
|
||||
)).Select(details => details.Confirmation));
|
||||
|
||||
if (ignoredConfirmations.Count == 0) {
|
||||
return await BotDatabase.MobileAuthenticator.HandleConfirmations(confirmations, accept).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
confirmations.ExceptWith(ignoredConfirmations);
|
||||
if (confirmations.Count == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return await BotDatabase.MobileAuthenticator.HandleConfirmations(confirmations, accept).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
internal async Task<bool> RefreshSession() {
|
||||
@@ -629,7 +633,10 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
if (CardsFarmer.CurrentGamesFarming.Count > 0) {
|
||||
return "Bot " + BotName + " is farming appIDs: " + string.Join(", ", CardsFarmer.CurrentGamesFarming) + " and has a total of " + CardsFarmer.GamesToFarm.Count + " games left to farm.";
|
||||
return "Bot " + BotName + " is farming appIDs: " + string.Join(", ", CardsFarmer.CurrentGamesFarming) +
|
||||
" and has a total of " + CardsFarmer.GamesToFarm.Count + " games left, " +
|
||||
CardsFarmer.GamesToFarm.Select(g => (int) g.CardsRemaining).DefaultIfEmpty(0).Sum() +
|
||||
" cards drop remaining to farm.";
|
||||
}
|
||||
|
||||
return "Bot " + BotName + " is not farming anything.";
|
||||
@@ -800,8 +807,11 @@ namespace ArchiSteamFarm {
|
||||
return "That bot doesn't have ASF 2FA enabled!";
|
||||
}
|
||||
|
||||
await AcceptConfirmations(confirm).ConfigureAwait(false);
|
||||
return "Done!";
|
||||
if (await AcceptConfirmations(confirm).ConfigureAwait(false)) {
|
||||
return "Success!";
|
||||
}
|
||||
|
||||
return "Something went wrong!";
|
||||
}
|
||||
|
||||
private static async Task<string> Response2FAConfirm(ulong steamID, string botName, bool confirm) {
|
||||
@@ -973,7 +983,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
bool alreadyHandled = false;
|
||||
foreach (Bot bot in Bots.Values.Where(bot => (bot != this) && bot.SteamClient.IsConnected && result.Items.Keys.Any(packageID => !bot.OwnedPackageIDs.Contains(packageID)))) {
|
||||
foreach (Bot bot in Bots.Values.Where(bot => (bot != this) && bot.SteamClient.IsConnected && ((result.Items.Count == 0) || result.Items.Keys.Any(packageID => !bot.OwnedPackageIDs.Contains(packageID))))) {
|
||||
|
||||
ArchiHandler.PurchaseResponseCallback otherResult = await bot.ArchiHandler.RedeemKey(key).ConfigureAwait(false);
|
||||
if (otherResult == null) {
|
||||
@@ -994,6 +1004,14 @@ namespace ArchiSteamFarm {
|
||||
if (alreadyHandled) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (result.Items.Count != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<uint, string> item in otherResult.Items) {
|
||||
result.Items[item.Key] = item.Value;
|
||||
}
|
||||
}
|
||||
|
||||
key = reader.ReadLine(); // Next key
|
||||
@@ -1602,7 +1620,6 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
bool acceptedSomething = false;
|
||||
foreach (ulong gid in callback.GuestPasses.Select(guestPass => guestPass["gid"].AsUnsignedLong()).Where(gid => (gid != 0) && !HandledGifts.Contains(gid))) {
|
||||
HandledGifts.Add(gid);
|
||||
|
||||
@@ -1610,19 +1627,14 @@ namespace ArchiSteamFarm {
|
||||
await LimitGiftsRequestsAsync().ConfigureAwait(false);
|
||||
|
||||
if (await ArchiWebHandler.AcceptGift(gid).ConfigureAwait(false)) {
|
||||
acceptedSomething = true;
|
||||
Logging.LogGenericInfo("Success!", BotName);
|
||||
} else {
|
||||
Logging.LogGenericInfo("Failed!", BotName);
|
||||
}
|
||||
}
|
||||
|
||||
if (acceptedSomething) {
|
||||
await CardsFarmer.OnNewGameAdded().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnLicenseList(SteamApps.LicenseListCallback callback) {
|
||||
private async void OnLicenseList(SteamApps.LicenseListCallback callback) {
|
||||
if (callback?.LicenseList == null) {
|
||||
Logging.LogNullError(nameof(callback) + " || " + nameof(callback.LicenseList), BotName);
|
||||
return;
|
||||
@@ -1635,6 +1647,9 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
OwnedPackageIDs.TrimExcess();
|
||||
|
||||
await Task.Delay(1000).ConfigureAwait(false); // Wait a second for eventual PlayingSessionStateCallback
|
||||
await CardsFarmer.OnNewGameAdded().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private void OnChatInvite(SteamFriends.ChatInviteCallback callback) {
|
||||
@@ -1838,9 +1853,6 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
Trading.CheckTrades().Forget();
|
||||
|
||||
await Task.Delay(1000).ConfigureAwait(false); // Wait a second for eventual PlayingSessionStateCallback
|
||||
CardsFarmer.StartFarming().Forget();
|
||||
break;
|
||||
case EResult.NoConnection:
|
||||
case EResult.ServiceUnavailable:
|
||||
@@ -1968,14 +1980,9 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnPurchaseResponse(ArchiHandler.PurchaseResponseCallback callback) {
|
||||
private void OnPurchaseResponse(ArchiHandler.PurchaseResponseCallback callback) {
|
||||
if (callback == null) {
|
||||
Logging.LogNullError(nameof(callback), BotName);
|
||||
return;
|
||||
}
|
||||
|
||||
if (callback.PurchaseResult == ArchiHandler.PurchaseResponseCallback.EPurchaseResult.OK) {
|
||||
await CardsFarmer.OnNewGameAdded().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,12 @@ namespace ArchiSteamFarm {
|
||||
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
|
||||
[SuppressMessage("ReSharper", "ConvertToConstant.Global")]
|
||||
internal sealed class BotConfig {
|
||||
internal enum EFarmingOrder : byte {
|
||||
Unordered,
|
||||
MostCardDropRemainingFirst,
|
||||
FewestCardDropRemainingFirst
|
||||
}
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal readonly bool Enabled = false;
|
||||
|
||||
@@ -68,6 +74,9 @@ namespace ArchiSteamFarm {
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal readonly bool DismissInventoryNotifications = true;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal readonly EFarmingOrder FarmingOrder = EFarmingOrder.Unordered;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal readonly bool FarmOffline = false;
|
||||
|
||||
@@ -113,7 +122,6 @@ namespace ArchiSteamFarm {
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal readonly HashSet<uint> GamesPlayedWhileIdle = new HashSet<uint>();
|
||||
|
||||
|
||||
internal static BotConfig Load(string filePath) {
|
||||
if (string.IsNullOrEmpty(filePath)) {
|
||||
Logging.LogNullError(nameof(filePath));
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
|
||||
using HtmlAgilityPack;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
@@ -35,10 +34,47 @@ using Newtonsoft.Json;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal sealed class CardsFarmer : IDisposable {
|
||||
internal sealed class Game {
|
||||
[JsonProperty]
|
||||
internal readonly uint AppID;
|
||||
|
||||
[JsonProperty]
|
||||
internal float HoursPlayed;
|
||||
|
||||
[JsonProperty]
|
||||
internal byte CardsRemaining;
|
||||
|
||||
internal Game(uint appID, float hoursPlayed, byte cardsRemaining) {
|
||||
if ((appID == 0) || (hoursPlayed < 0)) {
|
||||
throw new ArgumentOutOfRangeException(nameof(appID) + " || " + nameof(hoursPlayed));
|
||||
}
|
||||
|
||||
AppID = appID;
|
||||
HoursPlayed = hoursPlayed;
|
||||
CardsRemaining = cardsRemaining;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj) {
|
||||
if (ReferenceEquals(null, obj)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ReferenceEquals(this, obj)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return obj is Game && Equals((Game) obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode() => (int) AppID;
|
||||
|
||||
private bool Equals(Game other) => AppID == other.AppID;
|
||||
}
|
||||
|
||||
internal const byte MaxGamesPlayedConcurrently = 32; // This is limit introduced by Steam Network
|
||||
|
||||
[JsonProperty]
|
||||
internal readonly ConcurrentDictionary<uint, float> GamesToFarm = new ConcurrentDictionary<uint, float>();
|
||||
internal readonly ConcurrentHashSet<Game> GamesToFarm = new ConcurrentHashSet<Game>();
|
||||
|
||||
[JsonProperty]
|
||||
internal readonly ConcurrentHashSet<uint> CurrentGamesFarming = new ConcurrentHashSet<uint>();
|
||||
@@ -143,7 +179,8 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
} else {
|
||||
if (FarmMultiple()) {
|
||||
Logging.LogGenericInfo("Done farming: " + string.Join(", ", GamesToFarm.Keys), Bot.BotName);
|
||||
Logging.LogGenericInfo("Done farming: " + string.Join(", ", GamesToFarm.Select(g => g.AppID)),
|
||||
Bot.BotName);
|
||||
} else {
|
||||
NowFarming = false;
|
||||
return;
|
||||
@@ -153,7 +190,7 @@ namespace ArchiSteamFarm {
|
||||
} else { // If we have unrestricted card drops, we use simple algorithm
|
||||
Logging.LogGenericInfo("Chosen farming algorithm: Simple", Bot.BotName);
|
||||
while (GamesToFarm.Count > 0) {
|
||||
uint appID = GamesToFarm.Keys.FirstOrDefault();
|
||||
uint appID = GamesToFarm.First().AppID;
|
||||
if (await FarmSolo(appID).ConfigureAwait(false)) {
|
||||
continue;
|
||||
}
|
||||
@@ -218,7 +255,7 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Bot.BotConfig.CardDropsRestricted && (GamesToFarm.Count > 0) && (GamesToFarm.Values.Min() < 2)) {
|
||||
if (Bot.BotConfig.CardDropsRestricted && (GamesToFarm.Count > 0) && (GamesToFarm.Min(g => g.HoursPlayed) < 2)) {
|
||||
// If we have Complex algorithm and some games to boost, it's also worth to make a check
|
||||
// That's because we would check for new games after our current round anyway
|
||||
await StopFarming().ConfigureAwait(false);
|
||||
@@ -226,15 +263,15 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
private static HashSet<uint> GetGamesToFarmSolo(ConcurrentDictionary<uint, float> gamesToFarm) {
|
||||
private static HashSet<uint> GetGamesToFarmSolo(IEnumerable<Game> gamesToFarm) {
|
||||
if (gamesToFarm == null) {
|
||||
Logging.LogNullError(nameof(gamesToFarm));
|
||||
return null;
|
||||
}
|
||||
|
||||
HashSet<uint> result = new HashSet<uint>();
|
||||
foreach (KeyValuePair<uint, float> keyValue in gamesToFarm.Where(keyValue => keyValue.Value >= 2)) {
|
||||
result.Add(keyValue.Key);
|
||||
foreach (Game game in gamesToFarm.Where(g => g.HoursPlayed >= 2)) {
|
||||
result.Add(game.AppID);
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -271,6 +308,7 @@ namespace ArchiSteamFarm {
|
||||
CheckPage(htmlDocument);
|
||||
|
||||
if (maxPages == 1) {
|
||||
SortGamesToFarm();
|
||||
return GamesToFarm.Count > 0;
|
||||
}
|
||||
|
||||
@@ -283,9 +321,30 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
SortGamesToFarm();
|
||||
return GamesToFarm.Count > 0;
|
||||
}
|
||||
|
||||
private void SortGamesToFarm() {
|
||||
List<Game> gamesToFarm;
|
||||
switch (Bot.BotConfig.FarmingOrder) {
|
||||
case BotConfig.EFarmingOrder.MostCardDropRemainingFirst:
|
||||
gamesToFarm = GamesToFarm.OrderByDescending(g => g.CardsRemaining).ToList();
|
||||
break;
|
||||
|
||||
case BotConfig.EFarmingOrder.FewestCardDropRemainingFirst:
|
||||
gamesToFarm = GamesToFarm.OrderBy(g => g.CardsRemaining).ToList();
|
||||
break;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
GamesToFarm.Clear();
|
||||
GamesToFarm.AddRange(gamesToFarm);
|
||||
GamesToFarm.TrimExcess();
|
||||
}
|
||||
|
||||
private void CheckPage(HtmlDocument htmlDocument) {
|
||||
if (htmlDocument == null) {
|
||||
Logging.LogNullError(nameof(htmlDocument), Bot.BotName);
|
||||
@@ -303,6 +362,27 @@ namespace ArchiSteamFarm {
|
||||
continue; // This game is not needed for farming
|
||||
}
|
||||
|
||||
HtmlNode progressNode = htmlNode.SelectSingleNode(".//span[@class='progress_info_bold']");
|
||||
if (progressNode == null) {
|
||||
continue; // e.g. Holiday Sale 2015
|
||||
}
|
||||
|
||||
string progress = progressNode.InnerText;
|
||||
if (string.IsNullOrEmpty(progress)) {
|
||||
Logging.LogNullError(nameof(progress), Bot.BotName);
|
||||
return;
|
||||
}
|
||||
|
||||
byte cardsRemaining = 0;
|
||||
|
||||
Match progressMatch = Regex.Match(progress, @"\d+");
|
||||
if (progressMatch.Success) {
|
||||
if (!byte.TryParse(progressMatch.Value, out cardsRemaining)) {
|
||||
Logging.LogNullError(nameof(cardsRemaining), Bot.BotName);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
string steamLink = farmingNode.GetAttributeValue("href", null);
|
||||
if (string.IsNullOrEmpty(steamLink)) {
|
||||
Logging.LogNullError(nameof(steamLink), Bot.BotName);
|
||||
@@ -347,15 +427,15 @@ namespace ArchiSteamFarm {
|
||||
|
||||
float hours = 0;
|
||||
|
||||
Match match = Regex.Match(hoursString, @"[0-9\.,]+");
|
||||
if (match.Success) {
|
||||
if (!float.TryParse(match.Value, NumberStyles.Number, CultureInfo.InvariantCulture, out hours)) {
|
||||
Match hoursMatch = Regex.Match(hoursString, @"[0-9\.,]+");
|
||||
if (hoursMatch.Success) {
|
||||
if (!float.TryParse(hoursMatch.Value, NumberStyles.Number, CultureInfo.InvariantCulture, out hours)) {
|
||||
Logging.LogNullError(nameof(hours), Bot.BotName);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
GamesToFarm[appID] = hours;
|
||||
GamesToFarm.Add(new Game(appID, hours, cardsRemaining));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -414,6 +494,9 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
Game game = GamesToFarm.First(g => g.AppID == appID);
|
||||
game.CardsRemaining = cardsRemaining;
|
||||
|
||||
Logging.LogGenericInfo("Status for " + appID + ": " + cardsRemaining + " cards remaining", Bot.BotName);
|
||||
return cardsRemaining > 0;
|
||||
}
|
||||
@@ -424,10 +507,10 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
float maxHour = 0;
|
||||
foreach (KeyValuePair<uint, float> game in GamesToFarm) {
|
||||
CurrentGamesFarming.Add(game.Key);
|
||||
if (game.Value > maxHour) {
|
||||
maxHour = game.Value;
|
||||
foreach (Game game in GamesToFarm) {
|
||||
CurrentGamesFarming.Add(game.AppID);
|
||||
if (game.HoursPlayed > maxHour) {
|
||||
maxHour = game.HoursPlayed;
|
||||
}
|
||||
|
||||
if (CurrentGamesFarming.Count >= MaxGamesPlayedConcurrently) {
|
||||
@@ -464,12 +547,14 @@ namespace ArchiSteamFarm {
|
||||
return false;
|
||||
}
|
||||
|
||||
float hours;
|
||||
if (!GamesToFarm.TryRemove(appID, out hours)) {
|
||||
Game game = GamesToFarm.FirstOrDefault(g => g.AppID == appID);
|
||||
if (game == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
TimeSpan timeSpan = TimeSpan.FromHours(hours);
|
||||
GamesToFarm.Remove(game);
|
||||
|
||||
TimeSpan timeSpan = TimeSpan.FromHours(game.HoursPlayed);
|
||||
Logging.LogGenericInfo("Done farming: " + appID + " after " + timeSpan.ToString(@"hh\:mm") + " hours of playtime!", Bot.BotName);
|
||||
return true;
|
||||
}
|
||||
@@ -496,7 +581,8 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
// Don't forget to update our GamesToFarm hours
|
||||
GamesToFarm[appID] += (float) DateTime.Now.Subtract(startFarmingPeriod).TotalHours;
|
||||
Game game = GamesToFarm.First(g => g.AppID == appID);
|
||||
game.HoursPlayed += (float) DateTime.Now.Subtract(startFarmingPeriod).TotalHours;
|
||||
|
||||
if (!success) {
|
||||
break;
|
||||
@@ -529,8 +615,8 @@ namespace ArchiSteamFarm {
|
||||
|
||||
// Don't forget to update our GamesToFarm hours
|
||||
float timePlayed = (float) DateTime.Now.Subtract(startFarmingPeriod).TotalHours;
|
||||
foreach (uint appID in appIDs) {
|
||||
GamesToFarm[appID] += timePlayed;
|
||||
foreach (Game game in GamesToFarm.Where(game => appIDs.Contains(game.AppID))) {
|
||||
game.HoursPlayed += timePlayed;
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
@@ -36,6 +35,9 @@ namespace ArchiSteamFarm {
|
||||
public bool IsReadOnly => false;
|
||||
public IEnumerator<T> GetEnumerator() => new ConcurrentEnumerator<T>(HashSet, Lock);
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
void ICollection<T>.Add(T item) => Add(item);
|
||||
|
||||
public int Count {
|
||||
get {
|
||||
Lock.EnterReadLock();
|
||||
@@ -48,17 +50,6 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
[SuppressMessage("ReSharper", "UnusedMethodReturnValue.Global")]
|
||||
public bool Add(T item) {
|
||||
Lock.EnterWriteLock();
|
||||
|
||||
try {
|
||||
return HashSet.Add(item);
|
||||
} finally {
|
||||
Lock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear() {
|
||||
Lock.EnterWriteLock();
|
||||
|
||||
@@ -69,17 +60,6 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearAndTrim() {
|
||||
Lock.EnterWriteLock();
|
||||
|
||||
try {
|
||||
HashSet.Clear();
|
||||
HashSet.TrimExcess();
|
||||
} finally {
|
||||
Lock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
public bool Contains(T item) {
|
||||
Lock.EnterReadLock();
|
||||
|
||||
@@ -100,16 +80,6 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
public void TrimExcess() {
|
||||
Lock.EnterWriteLock();
|
||||
|
||||
try {
|
||||
HashSet.TrimExcess();
|
||||
} finally {
|
||||
Lock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() => Lock.Dispose();
|
||||
|
||||
public void CopyTo(T[] array, int arrayIndex) {
|
||||
@@ -122,8 +92,47 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
void ICollection<T>.Add(T item) => Add(item);
|
||||
internal void Add(T item) {
|
||||
Lock.EnterWriteLock();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
try {
|
||||
HashSet.Add(item);
|
||||
} finally {
|
||||
Lock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
internal void AddRange(IEnumerable<T> items) {
|
||||
Lock.EnterWriteLock();
|
||||
|
||||
try {
|
||||
foreach (T item in items) {
|
||||
HashSet.Add(item);
|
||||
}
|
||||
} finally {
|
||||
Lock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
internal void ClearAndTrim() {
|
||||
Lock.EnterWriteLock();
|
||||
|
||||
try {
|
||||
HashSet.Clear();
|
||||
HashSet.TrimExcess();
|
||||
} finally {
|
||||
Lock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
internal void TrimExcess() {
|
||||
Lock.EnterWriteLock();
|
||||
|
||||
try {
|
||||
HashSet.TrimExcess();
|
||||
} finally {
|
||||
Lock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal static class SharedInfo {
|
||||
internal const string Version = "2.1.3.2";
|
||||
internal const string Version = "2.1.3.5";
|
||||
internal const string Copyright = "Copyright © ArchiSteamFarm 2015-2016";
|
||||
|
||||
internal const string GithubRepo = "JustArchi/ArchiSteamFarm";
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"SteamMasterClanID": 0,
|
||||
"CardDropsRestricted": false,
|
||||
"DismissInventoryNotifications": true,
|
||||
"FarmingOrder": 0,
|
||||
"FarmOffline": false,
|
||||
"HandleOfflineMessages": false,
|
||||
"AcceptGifts": false,
|
||||
|
||||
38
CodeStyle.vssettings
Normal file
38
CodeStyle.vssettings
Normal file
File diff suppressed because one or more lines are too long
@@ -41,6 +41,12 @@ namespace ConfigGenerator {
|
||||
ProtectedDataForCurrentUser
|
||||
}
|
||||
|
||||
internal enum EFarmingOrder : byte {
|
||||
Unordered,
|
||||
MostCardDropRemainingFirst,
|
||||
FewestCardDropRemainingFirst
|
||||
}
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public bool Enabled { get; set; } = false;
|
||||
|
||||
@@ -75,6 +81,9 @@ namespace ConfigGenerator {
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public bool DismissInventoryNotifications { get; set; } = true;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public EFarmingOrder FarmingOrder { get; set; } = EFarmingOrder.Unordered;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public bool FarmOffline { get; set; } = false;
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user