Compare commits

..

13 Commits

Author SHA1 Message Date
JustArchi
d6c9fe3cde Add workarounds for #335 2016-08-21 22:35:31 +02:00
JustArchi
4b782bd10d Bump 2016-08-21 18:15:03 +02:00
JustArchi
1a1e48a33d Bump 2016-08-21 18:06:50 +02:00
JustArchi
f7d7c559b8 Derp 2016-08-21 18:04:48 +02:00
JustArchi
602cda73b7 Closes #335
We need to alter the flow in order to check for result updates on each loop instead of the beginning
2016-08-21 18:03:56 +02:00
Łukasz Domeradzki
f579462f60 Update CONTRIBUTING.md 2016-08-21 03:50:54 +02:00
JustArchi
8066f1a7c0 Don't attempt to loot foils if !IsBotAccount
Rationale: Foil cards are excluded from STM, as the price varies. For most people with !IsBotAccount that will be main account, which should never be lootable, and for people running bots for friends, making them keep foils makes sense. The main reason for this change is my own setup in which I'd like to automatically send all cards to ArchiBoT for 1:1 matching, but keep foils for selling.
2016-08-20 00:35:33 +02:00
JustArchi
f8409e1be6 Misc 2016-08-19 17:49:56 +02:00
Łukasz Domeradzki
c36eeb8c28 Update CONTRIBUTING.md 2016-08-19 15:11:49 +02:00
JustArchi
d0344a7ab9 Misc code review 2016-08-19 05:25:08 +02:00
JustArchi
2c767bfe85 Misc 2016-08-19 05:08:37 +02:00
JustArchi
2816ecaa90 Further optimize InMemoryServerListProvider and make it thread-safe 2016-08-19 04:57:21 +02:00
JustArchi
214746bca2 Optimize updating of server list
We can compare new endpoints firstly, to save Save() call if they're equal values-wise.
2016-08-19 04:10:49 +02:00
13 changed files with 142 additions and 62 deletions

View File

@@ -75,10 +75,7 @@ namespace ArchiSteamFarm {
return;
}
Notifications = new HashSet<ENotification>();
foreach (CMsgClientUserNotifications.Notification notification in msg.notifications) {
Notifications.Add((ENotification) notification.user_notification_type);
}
Notifications = new HashSet<ENotification>(msg.notifications.Select(notification => (ENotification) notification.user_notification_type));
}
internal NotificationsCallback(JobID jobID, CMsgClientItemAnnouncements msg) {

View File

@@ -345,14 +345,48 @@ namespace ArchiSteamFarm {
return response;
}
internal async Task<bool> HandleConfirmations(string deviceID, string confirmationHash, uint time, HashSet<MobileAuthenticator.Confirmation> confirmations, bool accept) {
if (string.IsNullOrEmpty(deviceID) || string.IsNullOrEmpty(confirmationHash) || (time == 0) || (confirmations == null) || (confirmations.Count == 0)) {
Logging.LogNullError(nameof(deviceID) + " || " + nameof(confirmationHash) + " || " + nameof(time) + " || " + nameof(confirmations), Bot.BotName);
return false;
internal async Task<bool?> HandleConfirmation(string deviceID, string confirmationHash, uint time, uint confirmationID, ulong confirmationKey, bool accept) {
if (string.IsNullOrEmpty(deviceID) || string.IsNullOrEmpty(confirmationHash) || (time == 0) || (confirmationID == 0) || (confirmationKey == 0)) {
Logging.LogNullError(nameof(deviceID) + " || " + nameof(confirmationHash) + " || " + nameof(time) + " || " + nameof(confirmationID) + " || " + nameof(confirmationKey), Bot.BotName);
return null;
}
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
return false;
return null;
}
string request = SteamCommunityURL + "/mobileconf/ajaxop?op=" + (accept ? "allow" : "cancel") + "&p=" + deviceID + "&a=" + SteamID + "&k=" + WebUtility.UrlEncode(confirmationHash) + "&t=" + time + "&m=android&tag=conf&cid=" + confirmationID + "&ck=" + confirmationKey;
string json = await WebBrowser.UrlGetToContentRetry(request).ConfigureAwait(false);
if (string.IsNullOrEmpty(json)) {
return null;
}
Steam.ConfirmationResponse response;
try {
response = JsonConvert.DeserializeObject<Steam.ConfirmationResponse>(json);
} catch (JsonException e) {
Logging.LogGenericException(e, Bot.BotName);
return null;
}
if (response != null) {
return response.Success;
}
Logging.LogNullError(nameof(response), Bot.BotName);
return null;
}
internal async Task<bool?> HandleConfirmations(string deviceID, string confirmationHash, uint time, HashSet<MobileAuthenticator.Confirmation> confirmations, bool accept) {
if (string.IsNullOrEmpty(deviceID) || string.IsNullOrEmpty(confirmationHash) || (time == 0) || (confirmations == null) || (confirmations.Count == 0)) {
Logging.LogNullError(nameof(deviceID) + " || " + nameof(confirmationHash) + " || " + nameof(time) + " || " + nameof(confirmations), Bot.BotName);
return null;
}
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
return null;
}
string request = SteamCommunityURL + "/mobileconf/multiajaxop";
@@ -374,7 +408,7 @@ namespace ArchiSteamFarm {
string json = await WebBrowser.UrlPostToContentRetry(request, data).ConfigureAwait(false);
if (string.IsNullOrEmpty(json)) {
return false;
return null;
}
Steam.ConfirmationResponse response;
@@ -383,7 +417,7 @@ namespace ArchiSteamFarm {
response = JsonConvert.DeserializeObject<Steam.ConfirmationResponse>(json);
} catch (JsonException e) {
Logging.LogGenericException(e, Bot.BotName);
return false;
return null;
}
if (response != null) {
@@ -391,7 +425,7 @@ namespace ArchiSteamFarm {
}
Logging.LogNullError(nameof(response), Bot.BotName);
return false;
return null;
}
internal async Task<Dictionary<uint, string>> GetOwnedGames() {

View File

@@ -780,10 +780,10 @@ namespace ArchiSteamFarm {
}
// Remove from our pending inventory all items that are not steam cards and boosters
inventory.RemoveWhere(item => (item.Type != Steam.Item.EType.TradingCard) && (item.Type != Steam.Item.EType.FoilTradingCard) && (item.Type != Steam.Item.EType.BoosterPack));
if (inventory.Count == 0) {
return "Nothing to send, inventory seems empty!";
if (inventory.RemoveWhere(item => (item.Type != Steam.Item.EType.TradingCard) && ((item.Type != Steam.Item.EType.FoilTradingCard) || !BotConfig.IsBotAccount) && (item.Type != Steam.Item.EType.BoosterPack)) > 0) {
if (inventory.Count == 0) {
return "Nothing to send, inventory seems empty!";
}
}
if (!await ArchiWebHandler.SendTradeOffer(inventory, BotConfig.SteamMasterID, BotConfig.SteamTradeToken).ConfigureAwait(false)) {
@@ -1040,8 +1040,7 @@ namespace ArchiSteamFarm {
}
bool alreadyHandled = false;
foreach (Bot bot in Bots.Where(bot => (bot.Value != this) && bot.Value.SteamClient.IsConnected && ((result.Items.Count == 0) || result.Items.Keys.Any(packageID => !bot.Value.OwnedPackageIDs.Contains(packageID)))).OrderBy(bot => bot.Key).Select(bot => bot.Value)) {
foreach (Bot bot in Bots.Where(bot => (bot.Value != this) && bot.Value.SteamClient.IsConnected).OrderBy(bot => bot.Key).Select(bot => bot.Value).Where(bot => (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) {
response.Append(Environment.NewLine + "<" + bot.BotName + "> Key: " + key + " | Status: Timeout!");

View File

@@ -32,6 +32,7 @@ using System.Linq;
namespace ArchiSteamFarm {
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
[SuppressMessage("ReSharper", "ConvertToConstant.Local")]
[SuppressMessage("ReSharper", "ConvertToConstant.Global")]
internal sealed class BotConfig {
internal enum EFarmingOrder : byte {
@@ -58,10 +59,6 @@ namespace ArchiSteamFarm {
[JsonProperty]
internal string SteamPassword { get; set; }
[JsonProperty(Required = Required.DisallowNull)]
[SuppressMessage("ReSharper", "ConvertToConstant.Local")]
private readonly CryptoHelper.ECryptoMethod PasswordFormat = CryptoHelper.ECryptoMethod.PlainText;
[JsonProperty]
internal string SteamParentalPIN { get; set; } = "0";
@@ -128,6 +125,9 @@ namespace ArchiSteamFarm {
[JsonProperty(Required = Required.DisallowNull)]
internal readonly HashSet<uint> GamesPlayedWhileIdle = new HashSet<uint>();
[JsonProperty(Required = Required.DisallowNull)]
private readonly CryptoHelper.ECryptoMethod PasswordFormat = CryptoHelper.ECryptoMethod.PlainText;
internal static BotConfig Load(string filePath) {
if (string.IsNullOrEmpty(filePath)) {
Logging.LogNullError(nameof(filePath));

View File

@@ -102,6 +102,21 @@ namespace ArchiSteamFarm {
}
}
internal bool ReplaceIfNeededWith(HashSet<T> items) {
Lock.EnterUpgradeableReadLock();
try {
if (HashSet.SetEquals(items)) {
return false;
}
ReplaceWith(items);
return true;
} finally {
Lock.ExitUpgradeableReadLock();
}
}
internal void ReplaceWith(IEnumerable<T> items) {
Lock.EnterWriteLock();

View File

@@ -25,14 +25,13 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Threading;
namespace ArchiSteamFarm {
internal sealed class GlobalDatabase {
internal sealed class GlobalDatabase : IDisposable {
private static readonly JsonSerializerSettings CustomSerializerSettings = new JsonSerializerSettings {
Converters = new List<JsonConverter> {
Converters = new List<JsonConverter>(2) {
new IPAddressConverter(),
new IPEndPointConverter()
}
@@ -56,7 +55,6 @@ namespace ArchiSteamFarm {
}
[JsonProperty(Required = Required.DisallowNull)]
[SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Local")]
internal readonly InMemoryServerListProvider ServerListProvider = new InMemoryServerListProvider();
private readonly object FileLock = new object();
@@ -91,7 +89,7 @@ namespace ArchiSteamFarm {
return globalDatabase;
}
private void OnServerListUpdated(object sender, EventArgs e) => Save();
public void Dispose() => ServerListProvider.Dispose();
// This constructor is used when creating new database
private GlobalDatabase(string filePath) : this() {
@@ -104,11 +102,12 @@ namespace ArchiSteamFarm {
}
// This constructor is used only by deserializer
[SuppressMessage("ReSharper", "UnusedMember.Local")]
private GlobalDatabase() {
ServerListProvider.ServerListUpdated += OnServerListUpdated;
}
private void OnServerListUpdated(object sender, EventArgs e) => Save();
private void Save() {
string json = JsonConvert.SerializeObject(this, CustomSerializerSettings);
if (string.IsNullOrEmpty(json)) {

View File

@@ -24,36 +24,36 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Threading.Tasks;
using Newtonsoft.Json;
using SteamKit2.Discovery;
namespace ArchiSteamFarm {
internal sealed class InMemoryServerListProvider : IServerListProvider {
internal sealed class InMemoryServerListProvider : IDisposable, IServerListProvider {
[JsonProperty(Required = Required.DisallowNull)]
[SuppressMessage("ReSharper", "FieldCanBeMadeReadOnly.Local")]
private HashSet<IPEndPoint> Servers = new HashSet<IPEndPoint>();
private readonly ConcurrentHashSet<IPEndPoint> Servers = new ConcurrentHashSet<IPEndPoint>();
internal event EventHandler ServerListUpdated = delegate { };
public Task<IEnumerable<IPEndPoint>> FetchServerListAsync() => Task.FromResult<IEnumerable<IPEndPoint>>(Servers);
public Task UpdateServerListAsync(IEnumerable<IPEndPoint> endpoints) {
if (endpoints == null) {
Logging.LogNullError(nameof(endpoints));
public Task UpdateServerListAsync(IEnumerable<IPEndPoint> endPoints) {
if (endPoints == null) {
Logging.LogNullError(nameof(endPoints));
return Task.Delay(0);
}
Servers.Clear();
foreach (IPEndPoint endpoint in endpoints) {
Servers.Add(endpoint);
HashSet<IPEndPoint> newServers = new HashSet<IPEndPoint>(endPoints);
if (!Servers.ReplaceIfNeededWith(newServers)) {
return Task.Delay(0);
}
ServerListUpdated(this, EventArgs.Empty);
return Task.Delay(0);
}
public void Dispose() => Servers.Dispose();
}
}

View File

@@ -29,7 +29,6 @@ using Newtonsoft.Json;
namespace ArchiSteamFarm.JSON {
internal static class GitHub {
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")]
internal sealed class ReleaseResponse {
#pragma warning disable 649
internal sealed class Asset {

View File

@@ -343,7 +343,6 @@ namespace ArchiSteamFarm.JSON {
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")]
internal sealed class ConfirmationResponse { // Deserialized from JSON
#pragma warning disable 649
[JsonProperty(PropertyName = "success", Required = Required.Always)]
@@ -355,7 +354,6 @@ namespace ArchiSteamFarm.JSON {
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")]
internal sealed class ConfirmationDetails { // Deserialized from JSON
internal enum EType : byte {
Unknown,

View File

@@ -36,7 +36,7 @@ using Newtonsoft.Json;
namespace ArchiSteamFarm {
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
internal sealed class MobileAuthenticator {
internal sealed class MobileAuthenticator : IDisposable {
internal sealed class Confirmation {
internal readonly uint ID;
internal readonly ulong Key;
@@ -66,7 +66,7 @@ namespace ArchiSteamFarm {
private static short SteamTimeDifference;
internal bool HasCorrectDeviceID => !string.IsNullOrEmpty(DeviceID) && !DeviceID.Equals("ERROR"); // "ERROR" is being used by SteamDesktopAuthenticator
private readonly SemaphoreSlim ConfirmationsSemaphore = new SemaphoreSlim(1);
#pragma warning disable 649
[JsonProperty(PropertyName = "shared_secret", Required = Required.Always)]
@@ -81,9 +81,9 @@ namespace ArchiSteamFarm {
private Bot Bot;
private MobileAuthenticator() {
internal bool HasCorrectDeviceID => !string.IsNullOrEmpty(DeviceID) && !DeviceID.Equals("ERROR"); // "ERROR" is being used by SteamDesktopAuthenticator
}
private MobileAuthenticator() { }
internal void Init(Bot bot) {
if (bot == null) {
@@ -113,19 +113,45 @@ namespace ArchiSteamFarm {
return false;
}
uint time = await GetSteamTime().ConfigureAwait(false);
if (time == 0) {
Logging.LogNullError(nameof(time), Bot.BotName);
return false;
}
await ConfirmationsSemaphore.WaitAsync().ConfigureAwait(false);
string confirmationHash = GenerateConfirmationKey(time, "conf");
if (!string.IsNullOrEmpty(confirmationHash)) {
return await Bot.ArchiWebHandler.HandleConfirmations(DeviceID, confirmationHash, time, confirmations, accept).ConfigureAwait(false);
}
try {
uint time = await GetSteamTime().ConfigureAwait(false);
if (time == 0) {
Logging.LogNullError(nameof(time), Bot.BotName);
return false;
}
Logging.LogNullError(nameof(confirmationHash), Bot.BotName);
return false;
string confirmationHash = GenerateConfirmationKey(time, "conf");
if (string.IsNullOrEmpty(confirmationHash)) {
Logging.LogNullError(nameof(confirmationHash), Bot.BotName);
return false;
}
bool? result = await Bot.ArchiWebHandler.HandleConfirmations(DeviceID, confirmationHash, time, confirmations, accept).ConfigureAwait(false);
if (!result.HasValue) { // Request timed out
return false;
}
if (result.Value) { // Request succeeded
return true;
}
// Our multi request failed, this is almost always Steam fuckup that happens randomly
// In this case, we'll accept all pending confirmations one-by-one, synchronously (as Steam can't handle them in parallel)
// We totally ignore actual result returned by those calls, abort only if request timed out
foreach (Confirmation confirmation in confirmations) {
bool? confirmationResult = await Bot.ArchiWebHandler.HandleConfirmation(DeviceID, confirmationHash, time, confirmation.ID, confirmation.Key, accept).ConfigureAwait(false);
if (!confirmationResult.HasValue) {
return false;
}
}
return true;
} finally {
ConfirmationsSemaphore.Release();
}
}
internal async Task<Steam.ConfirmationDetails> GetConfirmationDetails(Confirmation confirmation) {
@@ -334,7 +360,9 @@ namespace ArchiSteamFarm {
hash = hmac.ComputeHash(buffer);
}
return Convert.ToBase64String(hash, Base64FormattingOptions.None);
return Convert.ToBase64String(hash);
}
public void Dispose() => ConfirmationsSemaphore.Dispose();
}
}

View File

@@ -41,7 +41,7 @@ namespace ArchiSteamFarm {
WCFHostname
}
internal const string VersionNumber = "2.1.4.2";
internal const string VersionNumber = "2.1.4.4";
internal const string Copyright = "Copyright © ArchiSteamFarm 2015-2016";
internal const string GithubRepo = "JustArchi/ArchiSteamFarm";

View File

@@ -26,10 +26,12 @@ using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Net;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
namespace ArchiSteamFarm {
internal static class Utilities {
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[SuppressMessage("ReSharper", "UnusedParameter.Global")]
internal static void Forget(this Task task) { }

View File

@@ -1,16 +1,20 @@
# Contributing
Before making an issue or pull request, you should carefully read **[ASF wiki](https://github.com/JustArchi/ArchiSteamFarm/wiki)** first.
Before making an issue or pull request, you should carefully read **[ASF wiki](https://github.com/JustArchi/ArchiSteamFarm/wiki)** first. At least reading **[FAQ](https://github.com/JustArchi/ArchiSteamFarm/wiki/FAQ)** is mandatory.
## 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](https://gitter.im/JustArchi/ArchiSteamFarm)** and **[Steam group](http://steamcommunity.com/groups/ascfarm/discussions/1/)** 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.
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 neither bug reports should NOT be posted there. You have **[ASF chat](https://gitter.im/JustArchi/ArchiSteamFarm)** and **[Steam group](http://steamcommunity.com/groups/ascfarm/discussions/1/)** 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 and won't be answered.
---
### 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?
Before reporting a bug you should carefully check if the "bug" you're encountering is in fact ASF bug and not technical issue that is answered in the **[FAQ](https://github.com/JustArchi/ArchiSteamFarm/wiki/FAQ#issues)** or in other place on the wiki. Typically technical issue is intentional ASF behaviour which might not match your expectations, e.g. failing to send or accept steam trades - logic for accepting and sending steam trades is outside of the ASF, as stated in the FAQ, and there is no bug related to that because it's up to Steam to accept such request, or not. If you're not sure if you're encountering ASF bug or technical issue, please use **[ASF chat](https://gitter.im/JustArchi/ArchiSteamFarm)** or **[Steam group](http://steamcommunity.com/groups/ascfarm/discussions/1/)** and avoid GitHub issues.
Regarding ASF 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. If nobody is able to reproduce your bug, there is also no way of blindly fixing it.
It would also be cool if you could reproduce your issue on latest pre-release (and not stable) version, as this is most recent codebase that might include not-yet-released fix for your issue already. Of course, that is not mandatory, as ASF offers support for both latest pre-release as well as latest stable versions.
---
@@ -28,3 +32,8 @@ In general any pull request is welcome and should be accepted, unless there is a
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.
---
### Code style
Please stick with ASF code style when submitting PRs. In repo you can find standard **[VS settings](https://github.com/JustArchi/ArchiSteamFarm/blob/master/CodeStyle.vssettings)** file that you can use in Visual Studio for import. In addition to that, there is also **[DotSettings](https://github.com/JustArchi/ArchiSteamFarm/blob/master/ArchiSteamFarm.sln.DotSettings)** file for **[ReSharper](https://www.jetbrains.com/resharper/)** (optional). Consistency is the key.