Compare commits

..

9 Commits

Author SHA1 Message Date
JustArchi
dd621a90e4 Misc 2019-02-04 03:19:00 +01:00
JustArchi
761b46065c Extra optimization of MatchActively 2019-02-04 03:09:20 +01:00
dependabot[bot]
cfd655b851 Bump wiki from 92db2be to 22e3db6 (#1073)
Bumps [wiki](https://github.com/JustArchiNET/ArchiSteamFarm.wiki) from `92db2be` to `22e3db6`.
- [Release notes](https://github.com/JustArchiNET/ArchiSteamFarm.wiki/releases)
- [Commits](92db2be904...22e3db69e6)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-02-04 02:44:44 +01:00
dependabot[bot]
7c8086d200 Bump Microsoft.NET.Test.Sdk
Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 16.0.0-preview-20190124-02 to 16.0.0-preview-20190203-03.
- [Release notes](https://github.com/microsoft/vstest/releases)
- [Commits](https://github.com/microsoft/vstest/compare/v16.0.0-preview-20190124-02...v16.0.0-preview-20190203-03)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-02-04 01:44:14 +00:00
dependabot[bot]
6e624075e9 Bump ASF-ui from 6e32845 to 9aa6d5c
Bumps [ASF-ui](https://github.com/JustArchiNET/ASF-ui) from `6e32845` to `9aa6d5c`.
- [Release notes](https://github.com/JustArchiNET/ASF-ui/releases)
- [Commits](6e328457f5...9aa6d5cd28)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-02-04 01:33:34 +00:00
JustArchi
3c7d4716a6 Add previously-removed type 2019-02-02 22:56:51 +01:00
JustArchi
5ca35d0db4 Closes #1050 2019-02-02 22:54:23 +01:00
JustArchi
430ae31613 Implement more strict steamID checking 2019-02-01 22:41:25 +01:00
JustArchi
c8934ab669 Bump 2019-02-01 22:30:33 +01:00
15 changed files with 324 additions and 193 deletions

2
ASF-ui

Submodule ASF-ui updated: 6e328457f5...9aa6d5cd28

View File

@@ -36,7 +36,7 @@
<ItemGroup>
<PackageReference Include="ConfigureAwaitChecker.Analyzer" Version="3.0.0" />
<PackageReference Include="JetBrains.Annotations" Version="2018.3.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.0.0-preview-20190124-02" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.0.0-preview-20190203-03" />
<PackageReference Include="MSTest.TestAdapter" Version="1.4.0" />
<PackageReference Include="MSTest.TestFramework" Version="1.4.0" />
</ItemGroup>

View File

@@ -28,6 +28,30 @@ using static ArchiSteamFarm.Trading;
namespace ArchiSteamFarm.Tests {
[TestClass]
public sealed class Trading {
[TestMethod]
public void MismatchRarityIsNotFair() {
HashSet<Steam.Asset> itemsToGive = new HashSet<Steam.Asset> { CreateItem(1, rarity: Steam.Asset.ERarity.Rare) };
HashSet<Steam.Asset> itemsToReceive = new HashSet<Steam.Asset> { CreateItem(2) };
Assert.IsFalse(IsFairExchange(itemsToGive, itemsToReceive));
}
[TestMethod]
public void MismatchRealAppIDsIsNotFair() {
HashSet<Steam.Asset> itemsToGive = new HashSet<Steam.Asset> { CreateItem(1, realAppID: 570) };
HashSet<Steam.Asset> itemsToReceive = new HashSet<Steam.Asset> { CreateItem(2) };
Assert.IsFalse(IsFairExchange(itemsToGive, itemsToReceive));
}
[TestMethod]
public void MismatchTypesIsNotFair() {
HashSet<Steam.Asset> itemsToGive = new HashSet<Steam.Asset> { CreateItem(1, type: Steam.Asset.EType.Emoticon) };
HashSet<Steam.Asset> itemsToReceive = new HashSet<Steam.Asset> { CreateItem(2) };
Assert.IsFalse(IsFairExchange(itemsToGive, itemsToReceive));
}
[TestMethod]
public void MultiGameMultiTypeBadReject() {
HashSet<Steam.Asset> inventory = new HashSet<Steam.Asset> {
@@ -46,6 +70,7 @@ namespace ArchiSteamFarm.Tests {
CreateItem(3, realAppID: 730, type: Steam.Asset.EType.Emoticon)
};
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
}
@@ -66,6 +91,7 @@ namespace ArchiSteamFarm.Tests {
CreateItem(4, realAppID: 730, type: Steam.Asset.EType.Emoticon)
};
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
}
@@ -87,6 +113,7 @@ namespace ArchiSteamFarm.Tests {
CreateItem(4, realAppID: 730)
};
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
}
@@ -107,6 +134,7 @@ namespace ArchiSteamFarm.Tests {
CreateItem(4, realAppID: 730)
};
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
}
@@ -125,6 +153,7 @@ namespace ArchiSteamFarm.Tests {
CreateItem(3, type: Steam.Asset.EType.SteamGems)
};
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
}
@@ -146,6 +175,7 @@ namespace ArchiSteamFarm.Tests {
CreateItem(3, type: Steam.Asset.EType.Emoticon)
};
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
}
@@ -166,6 +196,7 @@ namespace ArchiSteamFarm.Tests {
CreateItem(4, type: Steam.Asset.EType.Emoticon)
};
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
}
@@ -185,6 +216,7 @@ namespace ArchiSteamFarm.Tests {
HashSet<Steam.Asset> itemsToReceive = new HashSet<Steam.Asset> { CreateItem(4, 3) };
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
}
@@ -202,6 +234,7 @@ namespace ArchiSteamFarm.Tests {
HashSet<Steam.Asset> itemsToReceive = new HashSet<Steam.Asset> { CreateItem(3, 3) };
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
}
@@ -219,6 +252,7 @@ namespace ArchiSteamFarm.Tests {
HashSet<Steam.Asset> itemsToReceive = new HashSet<Steam.Asset> { CreateItem(3, 2) };
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
}
@@ -232,6 +266,7 @@ namespace ArchiSteamFarm.Tests {
HashSet<Steam.Asset> itemsToGive = new HashSet<Steam.Asset> { CreateItem(1) };
HashSet<Steam.Asset> itemsToReceive = new HashSet<Steam.Asset> { CreateItem(2) };
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
}
@@ -250,6 +285,7 @@ namespace ArchiSteamFarm.Tests {
CreateItem(3)
};
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
}
@@ -264,6 +300,7 @@ namespace ArchiSteamFarm.Tests {
HashSet<Steam.Asset> itemsToGive = new HashSet<Steam.Asset> { CreateItem(2) };
HashSet<Steam.Asset> itemsToReceive = new HashSet<Steam.Asset> { CreateItem(3) };
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
}
@@ -287,6 +324,7 @@ namespace ArchiSteamFarm.Tests {
CreateItem(4)
};
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
}
@@ -296,6 +334,7 @@ namespace ArchiSteamFarm.Tests {
HashSet<Steam.Asset> itemsToGive = new HashSet<Steam.Asset> { CreateItem(1) };
HashSet<Steam.Asset> itemsToReceive = new HashSet<Steam.Asset> { CreateItem(2) };
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
}
@@ -305,6 +344,7 @@ namespace ArchiSteamFarm.Tests {
HashSet<Steam.Asset> itemsToGive = new HashSet<Steam.Asset> { CreateItem(1) };
HashSet<Steam.Asset> itemsToReceive = new HashSet<Steam.Asset> { CreateItem(2) };
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
}
@@ -322,10 +362,11 @@ namespace ArchiSteamFarm.Tests {
CreateItem(3)
};
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
}
[NotNull]
private static Steam.Asset CreateItem(ulong classID, uint amount = 1, bool marketable = true, uint realAppID = Steam.Asset.SteamAppID, Steam.Asset.EType type = Steam.Asset.EType.TradingCard) => new Steam.Asset(Steam.Asset.SteamAppID, Steam.Asset.SteamCommunityContextID, classID, amount, marketable, realAppID, type);
private static Steam.Asset CreateItem(ulong classID, uint amount = 1, bool marketable = true, uint realAppID = Steam.Asset.SteamAppID, Steam.Asset.EType type = Steam.Asset.EType.TradingCard, Steam.Asset.ERarity rarity = Steam.Asset.ERarity.Common) => new Steam.Asset(Steam.Asset.SteamAppID, Steam.Asset.SteamCommunityContextID, classID, amount, marketable, realAppID, type, rarity);
}
}

View File

@@ -250,7 +250,7 @@ namespace ArchiSteamFarm {
TradingScheduled = false;
}
HashSet<Steam.Asset> inventory = await Bot.ArchiWebHandler.GetInventory(Bot.SteamID, appID, contextID, true, wantedRealAppIDs, wantedTypes).ConfigureAwait(false);
HashSet<Steam.Asset> inventory = await Bot.ArchiWebHandler.GetInventory(Bot.SteamID, appID, contextID, tradable: true, wantedRealAppIDs: wantedRealAppIDs, wantedTypes: wantedTypes).ConfigureAwait(false);
if ((inventory == null) || (inventory.Count == 0)) {
return (false, string.Format(Strings.ErrorIsEmpty, nameof(inventory)));

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<ApplicationIcon>ASF.ico</ApplicationIcon>
<AssemblyVersion>4.0.0.2</AssemblyVersion>
<AssemblyVersion>4.0.0.3</AssemblyVersion>
<Authors>JustArchi</Authors>
<Company>JustArchi</Company>
<ConcurrentGarbageCollection>true</ConcurrentGarbageCollection>
@@ -11,7 +11,7 @@
<DefaultItemExcludes>$(DefaultItemExcludes);config/**;debug/**;out/**;overlay/**</DefaultItemExcludes>
<Description>ASF is an application that allows you to farm steam cards using multiple steam accounts simultaneously.</Description>
<ErrorReport>none</ErrorReport>
<FileVersion>4.0.0.2</FileVersion>
<FileVersion>4.0.0.3</FileVersion>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<LangVersion>latest</LangVersion>
<NoWarn>1591</NoWarn>

View File

@@ -119,7 +119,7 @@ namespace ArchiSteamFarm {
[ItemCanBeNull]
[PublicAPI]
[SuppressMessage("ReSharper", "FunctionComplexityOverflow")]
public async Task<HashSet<Steam.Asset>> GetInventory(ulong steamID = 0, uint appID = Steam.Asset.SteamAppID, uint contextID = Steam.Asset.SteamCommunityContextID, bool? tradable = null, IReadOnlyCollection<uint> wantedRealAppIDs = null, IReadOnlyCollection<Steam.Asset.EType> wantedTypes = null, IReadOnlyCollection<(uint AppID, Steam.Asset.EType Type)> wantedSets = null, IReadOnlyCollection<(uint AppID, Steam.Asset.EType Type)> skippedSets = null) {
public async Task<HashSet<Steam.Asset>> GetInventory(ulong steamID = 0, uint appID = Steam.Asset.SteamAppID, uint contextID = Steam.Asset.SteamCommunityContextID, bool? marketable = null, bool? tradable = null, IReadOnlyCollection<uint> wantedRealAppIDs = null, IReadOnlyCollection<Steam.Asset.EType> wantedTypes = null, IReadOnlyCollection<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity)> wantedSets = null) {
if ((appID == 0) || (contextID == 0)) {
Bot.ArchiLogger.LogNullError(nameof(appID) + " || " + nameof(contextID));
@@ -174,7 +174,7 @@ namespace ArchiSteamFarm {
return null;
}
Dictionary<ulong, (bool Marketable, bool Tradable, uint RealAppID, Steam.Asset.EType Type)> descriptions = new Dictionary<ulong, (bool Marketable, bool Tradable, uint RealAppID, Steam.Asset.EType Type)>();
Dictionary<ulong, (bool Marketable, bool Tradable, uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity)> descriptions = new Dictionary<ulong, (bool Marketable, bool Tradable, uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity)>();
foreach (Steam.InventoryResponse.Description description in response.Descriptions.Where(description => description != null)) {
if (description.ClassID == 0) {
@@ -187,25 +187,14 @@ namespace ArchiSteamFarm {
continue;
}
uint realAppID = 0;
Steam.Asset.EType type = Steam.Asset.EType.Unknown;
uint realAppID = description.RealAppID > 0 ? description.RealAppID : description.AppID;
if (appID == Steam.Asset.SteamAppID) {
if (!string.IsNullOrEmpty(description.MarketHashName)) {
realAppID = GetAppIDFromMarketHashName(description.MarketHashName);
}
if (!string.IsNullOrEmpty(description.Type)) {
type = GetItemType(description.Type);
}
}
descriptions[description.ClassID] = (description.Marketable, description.Tradable, realAppID, type);
descriptions[description.ClassID] = (description.Marketable, description.Tradable, realAppID, description.Type, description.Rarity);
}
foreach (Steam.Asset asset in response.Assets.Where(asset => asset != null)) {
if (descriptions.TryGetValue(asset.ClassID, out (bool Marketable, bool Tradable, uint RealAppID, Steam.Asset.EType Type) description)) {
if ((tradable.HasValue && (description.Tradable != tradable.Value)) || (wantedRealAppIDs?.Contains(description.RealAppID) == false) || (wantedSets?.Contains((description.RealAppID, description.Type)) == false) || (wantedTypes?.Contains(description.Type) == false) || (skippedSets?.Contains((description.RealAppID, description.Type)) == true)) {
if (descriptions.TryGetValue(asset.ClassID, out (bool Marketable, bool Tradable, uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity) description)) {
if ((marketable.HasValue && (description.Marketable != marketable.Value)) || (tradable.HasValue && (description.Tradable != tradable.Value)) || (wantedRealAppIDs?.Contains(description.RealAppID) == false) || (wantedTypes?.Contains(description.Type) == false) || (wantedSets?.Contains((description.RealAppID, description.Type, description.Rarity)) == false)) {
continue;
}
@@ -213,7 +202,8 @@ namespace ArchiSteamFarm {
asset.Tradable = description.Tradable;
asset.RealAppID = description.RealAppID;
asset.Type = description.Type;
} else if (tradable.HasValue || (wantedRealAppIDs != null) || (wantedSets != null) || (wantedTypes != null) || (skippedSets != null)) {
asset.Rarity = description.Rarity;
} else if (marketable.HasValue || tradable.HasValue || (wantedRealAppIDs != null) || (wantedTypes != null) || (wantedSets != null)) {
continue;
}
@@ -1145,7 +1135,7 @@ namespace ArchiSteamFarm {
internal HttpClient GenerateDisposableHttpClient() => WebBrowser.GenerateDisposableHttpClient();
[ItemCanBeNull]
internal async Task<HashSet<uint>> GenerateNewDiscoveryQueue() {
internal async Task<ImmutableHashSet<uint>> GenerateNewDiscoveryQueue() {
const string request = "/explore/generatenewdiscoveryqueue";
// Extra entry for sessionID
@@ -1196,7 +1186,7 @@ namespace ArchiSteamFarm {
return null;
}
Dictionary<(uint AppID, ulong ClassID), (bool Marketable, uint RealAppID, Steam.Asset.EType Type)> descriptions = new Dictionary<(uint AppID, ulong ClassID), (bool Marketable, uint RealAppID, Steam.Asset.EType Type)>();
Dictionary<(uint AppID, ulong ClassID), (bool Marketable, uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity)> descriptions = new Dictionary<(uint AppID, ulong ClassID), (bool Marketable, uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity)>();
foreach (KeyValue description in response["descriptions"].Children) {
uint appID = description["appid"].AsUnsignedInteger();
@@ -1221,24 +1211,51 @@ namespace ArchiSteamFarm {
bool marketable = description["marketable"].AsBoolean();
uint realAppID = 0;
Steam.Asset.EType type = Steam.Asset.EType.Unknown;
uint realAppID = appID;
KeyValue marketFeeApp = description["market_fee_app"];
if (appID == Steam.Asset.SteamAppID) {
string hashName = description["market_hash_name"].Value;
if (marketFeeApp != KeyValue.Invalid) {
realAppID = description["market_fee_app"].AsUnsignedInteger();
if (!string.IsNullOrEmpty(hashName)) {
realAppID = GetAppIDFromMarketHashName(hashName);
}
if (realAppID == 0) {
Bot.ArchiLogger.LogNullError(nameof(realAppID));
string descriptionType = description["type"].Value;
if (!string.IsNullOrEmpty(descriptionType)) {
type = GetItemType(descriptionType);
return null;
}
}
descriptions[(appID, classID)] = (marketable, realAppID, type);
Steam.Asset.EType type = Steam.Asset.EType.Unknown;
Steam.Asset.ERarity rarity = Steam.Asset.ERarity.Unknown;
List<KeyValue> tags = description["tags"].Children;
if (tags.Count > 0) {
HashSet<Steam.InventoryResponse.Description.Tag> parsedTags = new HashSet<Steam.InventoryResponse.Description.Tag>();
foreach (KeyValue tag in tags) {
string identifier = tag["category"].AsString();
if (string.IsNullOrEmpty(identifier)) {
Bot.ArchiLogger.LogNullError(nameof(identifier));
return null;
}
string value = tag["internal_name"].AsString();
if (string.IsNullOrEmpty(value)) {
Bot.ArchiLogger.LogNullError(nameof(value));
return null;
}
parsedTags.Add(new Steam.InventoryResponse.Description.Tag(identifier, value));
}
(type, rarity) = Steam.InventoryResponse.Description.InterpretTags(parsedTags);
}
descriptions[(appID, classID)] = (marketable, realAppID, type, rarity);
}
HashSet<Steam.TradeOffer> result = new HashSet<Steam.TradeOffer>();
@@ -1612,7 +1629,7 @@ namespace ArchiSteamFarm {
return null;
}
result[appID] = game["name"].Value;
result[appID] = game["name"].AsString();
}
return result;
@@ -1911,7 +1928,7 @@ namespace ArchiSteamFarm {
return false;
}
string steamLogin = response["token"].Value;
string steamLogin = response["token"].AsString();
if (string.IsNullOrEmpty(steamLogin)) {
Bot.ArchiLogger.LogNullError(nameof(steamLogin));
@@ -1919,7 +1936,7 @@ namespace ArchiSteamFarm {
return false;
}
string steamLoginSecure = response["tokensecure"].Value;
string steamLoginSecure = response["tokensecure"].AsString();
if (string.IsNullOrEmpty(steamLoginSecure)) {
Bot.ArchiLogger.LogNullError(nameof(steamLoginSecure));
@@ -2241,76 +2258,6 @@ namespace ArchiSteamFarm {
return (ESteamApiKeyState.Registered, text);
}
private static uint GetAppIDFromMarketHashName(string hashName) {
if (string.IsNullOrEmpty(hashName)) {
ASF.ArchiLogger.LogNullError(nameof(hashName));
return 0;
}
int index = hashName.IndexOf('-');
if (index <= 0) {
ASF.ArchiLogger.LogGenericError(string.Format(Strings.WarningUnknownValuePleaseReport, nameof(hashName), hashName));
return 0;
}
if (!uint.TryParse(hashName.Substring(0, index), out uint appID) || (appID == 0)) {
ASF.ArchiLogger.LogNullError(nameof(appID));
return 0;
}
return appID;
}
private static Steam.Asset.EType GetItemType(string name) {
if (string.IsNullOrEmpty(name)) {
ASF.ArchiLogger.LogNullError(nameof(name));
return Steam.Asset.EType.Unknown;
}
switch (name) {
case "Booster Pack":
return Steam.Asset.EType.BoosterPack;
case "Steam Gems":
return Steam.Asset.EType.SteamGems;
default:
if (name.EndsWith("Consumable", StringComparison.Ordinal)) {
return Steam.Asset.EType.Consumable;
}
if (name.EndsWith("Emoticon", StringComparison.Ordinal)) {
return Steam.Asset.EType.Emoticon;
}
if (name.EndsWith("Foil Trading Card", StringComparison.Ordinal)) {
return Steam.Asset.EType.FoilTradingCard;
}
if (name.EndsWith("Profile Background", StringComparison.Ordinal)) {
return Steam.Asset.EType.ProfileBackground;
}
if (name.EndsWith("Sale Item", StringComparison.Ordinal)) {
return Steam.Asset.EType.SaleItem;
}
if (name.EndsWith("Trading Card", StringComparison.Ordinal)) {
return Steam.Asset.EType.TradingCard;
}
ASF.ArchiLogger.LogGenericError(string.Format(Strings.WarningUnknownValuePleaseReport, nameof(name), name));
return Steam.Asset.EType.Unknown;
}
}
private async Task<bool> IsProfileUri(Uri uri, bool waitForInitialization = true) {
if (uri == null) {
ASF.ArchiLogger.LogNullError(nameof(uri));
@@ -2383,7 +2330,7 @@ namespace ArchiSteamFarm {
return uri.AbsolutePath.StartsWith("/login", StringComparison.Ordinal) || uri.Host.Equals("lostauth");
}
private static bool ParseItems(IReadOnlyDictionary<(uint AppID, ulong ClassID), (bool Marketable, uint RealAppID, Steam.Asset.EType Type)> descriptions, IReadOnlyCollection<KeyValue> input, ICollection<Steam.Asset> output) {
private static bool ParseItems(IReadOnlyDictionary<(uint AppID, ulong ClassID), (bool Marketable, uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity)> descriptions, IReadOnlyCollection<KeyValue> input, ICollection<Steam.Asset> output) {
if ((descriptions == null) || (input == null) || (input.Count == 0) || (output == null)) {
ASF.ArchiLogger.LogNullError(nameof(descriptions) + " || " + nameof(input) + " || " + nameof(output));
@@ -2426,14 +2373,16 @@ namespace ArchiSteamFarm {
bool marketable = true;
uint realAppID = 0;
Steam.Asset.EType type = Steam.Asset.EType.Unknown;
Steam.Asset.ERarity rarity = Steam.Asset.ERarity.Unknown;
if (descriptions.TryGetValue((appID, classID), out (bool Marketable, uint RealAppID, Steam.Asset.EType Type) description)) {
if (descriptions.TryGetValue((appID, classID), out (bool Marketable, uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity) description)) {
marketable = description.Marketable;
realAppID = description.RealAppID;
type = description.Type;
rarity = description.Rarity;
}
Steam.Asset steamAsset = new Steam.Asset(appID, contextID, classID, amount, marketable, realAppID, type);
Steam.Asset steamAsset = new Steam.Asset(appID, contextID, classID, amount, marketable, realAppID, type, rarity);
output.Add(steamAsset);
}

View File

@@ -293,8 +293,14 @@ namespace ArchiSteamFarm {
return (false, string.Format(Strings.ErrorConfigPropertyInvalid, nameof(SteamParentalCode), SteamParentalCode));
}
foreach (EPermission permission in SteamUserPermissions.Values.Where(permission => !Enum.IsDefined(typeof(EPermission), permission))) {
return (false, string.Format(Strings.ErrorConfigPropertyInvalid, nameof(SteamUserPermissions), permission));
foreach ((ulong steamID, EPermission permission) in SteamUserPermissions) {
if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) {
return (false, string.Format(Strings.ErrorConfigPropertyInvalid, nameof(SteamUserPermissions), steamID));
}
if (!Enum.IsDefined(typeof(EPermission), permission)) {
return (false, string.Format(Strings.ErrorConfigPropertyInvalid, nameof(SteamUserPermissions), permission));
}
}
return TradingPreferences <= ETradingPreferences.All ? (true, null) : (false, string.Format(Strings.ErrorConfigPropertyInvalid, nameof(TradingPreferences), TradingPreferences));

View File

@@ -20,7 +20,7 @@
// limitations under the License.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
@@ -42,7 +42,7 @@ namespace ArchiSteamFarm {
return await GetReleaseFromURL(releaseURL).ConfigureAwait(false);
}
List<ReleaseResponse> response = await GetReleasesFromURL(releaseURL).ConfigureAwait(false);
ImmutableList<ReleaseResponse> response = await GetReleasesFromURL(releaseURL).ConfigureAwait(false);
return response?.FirstOrDefault();
}
@@ -59,7 +59,7 @@ namespace ArchiSteamFarm {
}
[ItemCanBeNull]
internal static async Task<List<ReleaseResponse>> GetReleases(byte count) {
internal static async Task<ImmutableList<ReleaseResponse>> GetReleases(byte count) {
if (count == 0) {
ASF.ArchiLogger.LogNullError(nameof(count));
@@ -104,14 +104,14 @@ namespace ArchiSteamFarm {
}
[ItemCanBeNull]
private static async Task<List<ReleaseResponse>> GetReleasesFromURL(string releaseURL) {
private static async Task<ImmutableList<ReleaseResponse>> GetReleasesFromURL(string releaseURL) {
if (string.IsNullOrEmpty(nameof(releaseURL))) {
ASF.ArchiLogger.LogNullError(nameof(releaseURL));
return null;
}
WebBrowser.ObjectResponse<List<ReleaseResponse>> objectResponse = await ASF.WebBrowser.UrlGetToJsonObject<List<ReleaseResponse>>(releaseURL).ConfigureAwait(false);
WebBrowser.ObjectResponse<ImmutableList<ReleaseResponse>> objectResponse = await ASF.WebBrowser.UrlGetToJsonObject<ImmutableList<ReleaseResponse>>(releaseURL).ConfigureAwait(false);
return objectResponse?.Content;
}
@@ -119,7 +119,7 @@ namespace ArchiSteamFarm {
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
internal sealed class ReleaseResponse {
[JsonProperty(PropertyName = "assets", Required = Required.Always)]
internal readonly HashSet<Asset> Assets;
internal readonly ImmutableHashSet<Asset> Assets;
[JsonProperty(PropertyName = "prerelease", Required = Required.Always)]
internal readonly bool IsPreRelease;

View File

@@ -257,6 +257,10 @@ namespace ArchiSteamFarm {
return (false, string.Format(Strings.ErrorConfigPropertyInvalid, nameof(SteamMessagePrefix), SteamMessagePrefix));
}
if ((SteamOwnerID != 0) && !new SteamID(SteamOwnerID).IsIndividualAccount) {
return (false, string.Format(Strings.ErrorConfigPropertyInvalid, nameof(SteamOwnerID), SteamOwnerID));
}
if ((SteamProtocols <= 0) || (SteamProtocols > ProtocolTypes.All)) {
return (false, string.Format(Strings.ErrorConfigPropertyInvalid, nameof(SteamProtocols), SteamProtocols));
}

View File

@@ -21,6 +21,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
@@ -79,7 +80,7 @@ namespace ArchiSteamFarm.IPC.Controllers.Api {
return BadRequest(new GenericResponse<IReadOnlyCollection<GitHubReleaseResponse>>(false, string.Format(Strings.ErrorIsEmpty, nameof(count))));
}
List<GitHub.ReleaseResponse> response = await GitHub.GetReleases(count).ConfigureAwait(false);
ImmutableList<GitHub.ReleaseResponse> response = await GitHub.GetReleases(count).ConfigureAwait(false);
if ((response == null) || (response.Count == 0)) {
return BadRequest(new GenericResponse<IReadOnlyCollection<GitHub.ReleaseResponse>>(false, string.Format(Strings.ErrorRequestFailedTooManyTimes, WebBrowser.MaxTries)));

View File

@@ -21,6 +21,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using ArchiSteamFarm.Localization;
@@ -57,6 +58,9 @@ namespace ArchiSteamFarm.Json {
[PublicAPI]
public bool Marketable { get; internal set; }
[PublicAPI]
public ERarity Rarity { get; internal set; }
[PublicAPI]
public uint RealAppID { get; internal set; }
@@ -160,7 +164,7 @@ namespace ArchiSteamFarm.Json {
}
// Constructed from trades being received or plugins
public Asset(uint appID, uint contextID, ulong classID, uint amount, bool marketable = true, uint realAppID = 0, EType type = EType.Unknown) {
public Asset(uint appID, uint contextID, ulong classID, uint amount, bool marketable = true, uint realAppID = 0, EType type = EType.Unknown, ERarity rarity = ERarity.Unknown) {
if ((appID == 0) || (contextID == 0) || (classID == 0) || (amount == 0)) {
throw new ArgumentNullException(nameof(appID) + " || " + nameof(contextID) + " || " + nameof(classID) + " || " + nameof(amount));
}
@@ -172,11 +176,19 @@ namespace ArchiSteamFarm.Json {
Marketable = marketable;
RealAppID = realAppID;
Type = type;
Rarity = rarity;
}
[JsonConstructor]
private Asset() { }
public enum ERarity : byte {
Unknown,
Common,
Uncommon,
Rare
}
public enum EType : byte {
Unknown,
BoosterPack,
@@ -186,6 +198,8 @@ namespace ArchiSteamFarm.Json {
TradingCard,
SteamGems,
SaleItem,
// ReSharper disable once UnusedMember.Global - TODO: This type exists, but we don't know what item class it has, maybe we can fill it when consumables return in one of the future sales
Consumable
}
}
@@ -391,10 +405,10 @@ namespace ArchiSteamFarm.Json {
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
internal sealed class InventoryResponse : NumberResponse {
[JsonProperty(PropertyName = "assets", Required = Required.DisallowNull)]
internal readonly HashSet<Asset> Assets;
internal readonly ImmutableHashSet<Asset> Assets;
[JsonProperty(PropertyName = "descriptions", Required = Required.DisallowNull)]
internal readonly HashSet<Description> Descriptions;
internal readonly ImmutableHashSet<Description> Descriptions;
[JsonProperty(PropertyName = "error", Required = Required.DisallowNull)]
internal readonly string Error;
@@ -436,15 +450,14 @@ namespace ArchiSteamFarm.Json {
[JsonProperty(PropertyName = "appid", Required = Required.Always)]
internal readonly uint AppID;
[JsonProperty(PropertyName = "market_hash_name", Required = Required.Always)]
internal readonly string MarketHashName;
[JsonProperty(PropertyName = "type", Required = Required.Always)]
internal readonly string Type;
[JsonProperty(PropertyName = "market_fee_app", Required = Required.DisallowNull)]
internal readonly uint RealAppID;
internal ulong ClassID { get; private set; }
internal bool Marketable { get; private set; }
internal Asset.ERarity Rarity { get; private set; }
internal bool Tradable { get; private set; }
internal Asset.EType Type { get; private set; }
[JsonProperty(PropertyName = "classid", Required = Required.Always)]
private string ClassIDText {
@@ -470,6 +483,19 @@ namespace ArchiSteamFarm.Json {
set => Marketable = value > 0;
}
[JsonProperty(PropertyName = "tags", Required = Required.DisallowNull)]
private ImmutableHashSet<Tag> Tags {
set {
if ((value == null) || (value.Count == 0)) {
ASF.ArchiLogger.LogNullError(nameof(value));
return;
}
(Type, Rarity) = InterpretTags(value);
}
}
[JsonProperty(PropertyName = "tradable", Required = Required.Always)]
private byte TradableNumber {
set => Tradable = value > 0;
@@ -477,13 +503,129 @@ namespace ArchiSteamFarm.Json {
[JsonConstructor]
private Description() { }
internal static (Asset.EType Type, Asset.ERarity Rarity) InterpretTags(IReadOnlyCollection<Tag> tags) {
if ((tags == null) || (tags.Count == 0)) {
ASF.ArchiLogger.LogNullError(nameof(tags));
return (Asset.EType.Unknown, Asset.ERarity.Unknown);
}
Asset.EType type = Asset.EType.Unknown;
Asset.ERarity rarity = Asset.ERarity.Unknown;
foreach (Tag tag in tags) {
switch (tag.Identifier) {
case "cardborder":
switch (tag.Value) {
case "cardborder_0":
type = Asset.EType.TradingCard;
break;
case "cardborder_1":
type = Asset.EType.FoilTradingCard;
break;
default:
ASF.ArchiLogger.LogGenericError(string.Format(Strings.WarningUnknownValuePleaseReport, nameof(tag.Value), tag.Value));
break;
}
break;
case "droprate":
switch (tag.Value) {
case "droprate_0":
rarity = Asset.ERarity.Common;
break;
case "droprate_1":
rarity = Asset.ERarity.Uncommon;
break;
case "droprate_2":
rarity = Asset.ERarity.Rare;
break;
default:
ASF.ArchiLogger.LogGenericError(string.Format(Strings.WarningUnknownValuePleaseReport, nameof(tag.Value), tag.Value));
break;
}
break;
case "item_class":
switch (tag.Value) {
case "item_class_2":
// This is a fallback in case we'd have no cardborder available to interpret
if (type == Asset.EType.Unknown) {
type = Asset.EType.TradingCard;
}
break;
case "item_class_3":
type = Asset.EType.ProfileBackground;
break;
case "item_class_4":
type = Asset.EType.Emoticon;
break;
case "item_class_5":
type = Asset.EType.BoosterPack;
break;
case "item_class_7":
type = Asset.EType.SteamGems;
break;
case "item_class_10":
type = Asset.EType.SaleItem;
break;
default:
ASF.ArchiLogger.LogGenericError(string.Format(Strings.WarningUnknownValuePleaseReport, nameof(tag.Value), tag.Value));
break;
}
break;
}
}
return (type, rarity);
}
internal sealed class Tag {
[JsonProperty(PropertyName = "category", Required = Required.Always)]
internal readonly string Identifier;
[JsonProperty(PropertyName = "internal_name", Required = Required.Always)]
internal readonly string Value;
internal Tag([NotNull] string identifier, [NotNull] string value) {
if (string.IsNullOrEmpty(identifier) || string.IsNullOrEmpty(value)) {
throw new ArgumentNullException(nameof(identifier) + " || " + nameof(value));
}
Identifier = identifier;
Value = value;
}
[JsonConstructor]
private Tag() { }
}
}
}
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
internal sealed class NewDiscoveryQueueResponse {
[JsonProperty(PropertyName = "queue", Required = Required.Always)]
internal readonly HashSet<uint> Queue;
internal readonly ImmutableHashSet<uint> Queue;
[JsonConstructor]
private NewDiscoveryQueueResponse() { }

View File

@@ -184,10 +184,10 @@ namespace ArchiSteamFarm {
}
[ItemCanBeNull]
private async Task<HashSet<ListedUser>> GetListedUsers() {
private async Task<ImmutableHashSet<ListedUser>> GetListedUsers() {
const string request = URL + "/Api/Bots";
WebBrowser.ObjectResponse<HashSet<ListedUser>> objectResponse = await Bot.ArchiWebHandler.WebBrowser.UrlGetToJsonObject<HashSet<ListedUser>>(request).ConfigureAwait(false);
WebBrowser.ObjectResponse<ImmutableHashSet<ListedUser>> objectResponse = await Bot.ArchiWebHandler.WebBrowser.UrlGetToJsonObject<ImmutableHashSet<ListedUser>>(request).ConfigureAwait(false);
return objectResponse?.Content;
}
@@ -300,7 +300,7 @@ namespace ArchiSteamFarm {
return false;
}
(Dictionary<(uint AppID, Steam.Asset.EType Type), Dictionary<ulong, uint>> fullState, Dictionary<(uint AppID, Steam.Asset.EType Type), Dictionary<ulong, uint>> tradableState) = Trading.GetDividedInventoryState(ourInventory);
(Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), Dictionary<ulong, uint>> fullState, Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), Dictionary<ulong, uint>> tradableState) = Trading.GetDividedInventoryState(ourInventory);
if (Trading.IsEmptyForMatching(fullState, tradableState)) {
// User doesn't have any more dupes in the inventory
@@ -309,7 +309,7 @@ namespace ArchiSteamFarm {
return false;
}
HashSet<ListedUser> listedUsers = await GetListedUsers().ConfigureAwait(false);
ImmutableHashSet<ListedUser> listedUsers = await GetListedUsers().ConfigureAwait(false);
if ((listedUsers == null) || (listedUsers.Count == 0)) {
Bot.ArchiLogger.LogGenericTrace(string.Format(Strings.ErrorIsEmpty, nameof(listedUsers)));
@@ -318,12 +318,18 @@ namespace ArchiSteamFarm {
}
byte emptyMatches = 0;
HashSet<(uint AppID, Steam.Asset.EType Type)> skippedSetsThisRound = new HashSet<(uint AppID, Steam.Asset.EType Type)>();
HashSet<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity)> skippedSetsThisRound = new HashSet<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity)>();
foreach (ListedUser listedUser in listedUsers.Where(listedUser => listedUser.MatchEverything && acceptedMatchableTypes.Any(listedUser.MatchableTypes.Contains) && (!triedSteamIDs.TryGetValue(listedUser.SteamID, out (byte Tries, ISet<ulong> GivenAssetIDs, ISet<ulong> ReceivedAssetIDs) attempt) || (attempt.Tries < byte.MaxValue)) && !Bot.IsBlacklistedFromTrades(listedUser.SteamID)).OrderBy(listedUser => triedSteamIDs.TryGetValue(listedUser.SteamID, out (byte Tries, ISet<ulong> GivenAssetIDs, ISet<ulong> ReceivedAssetIDs) attempt) ? attempt.Tries : 0).ThenByDescending(listedUser => listedUser.Score).Take(MaxMatchedBotsHard)) {
HashSet<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity)> wantedSets = tradableState.Keys.Where(set => !skippedSetsThisRound.Contains(set) && listedUser.MatchableTypes.Contains(set.Type)).ToHashSet();
if (wantedSets.Count == 0) {
continue;
}
Bot.ArchiLogger.LogGenericTrace(listedUser.SteamID + "...");
HashSet<Steam.Asset> theirInventory = await Bot.ArchiWebHandler.GetInventory(listedUser.SteamID, tradable: true, wantedSets: fullState.Keys, skippedSets: skippedSetsThisRound).ConfigureAwait(false);
HashSet<Steam.Asset> theirInventory = await Bot.ArchiWebHandler.GetInventory(listedUser.SteamID, tradable: true, wantedSets: wantedSets).ConfigureAwait(false);
if ((theirInventory == null) || (theirInventory.Count == 0)) {
Bot.ArchiLogger.LogGenericTrace(string.Format(Strings.ErrorIsEmpty, nameof(theirInventory)));
@@ -331,19 +337,19 @@ namespace ArchiSteamFarm {
continue;
}
HashSet<(uint AppID, Steam.Asset.EType Type)> skippedSetsThisUser = new HashSet<(uint AppID, Steam.Asset.EType Type)>();
HashSet<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity)> skippedSetsThisUser = new HashSet<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity)>();
Dictionary<(uint AppID, Steam.Asset.EType Type), Dictionary<ulong, uint>> theirTradableState = Trading.GetInventoryState(theirInventory);
Dictionary<(uint AppID, Steam.Asset.EType Type), Dictionary<ulong, uint>> inventoryStateChanges = new Dictionary<(uint AppID, Steam.Asset.EType Type), Dictionary<ulong, uint>>();
Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), Dictionary<ulong, uint>> theirTradableState = Trading.GetInventoryState(theirInventory);
Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), Dictionary<ulong, uint>> inventoryStateChanges = new Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), Dictionary<ulong, uint>>();
for (byte i = 0; i < Trading.MaxTradesPerAccount; i++) {
byte itemsInTrade = 0;
HashSet<(uint AppID, Steam.Asset.EType Type)> skippedSetsThisTrade = new HashSet<(uint AppID, Steam.Asset.EType Type)>();
HashSet<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity)> skippedSetsThisTrade = new HashSet<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity)>();
Dictionary<ulong, uint> classIDsToGive = new Dictionary<ulong, uint>();
Dictionary<ulong, uint> classIDsToReceive = new Dictionary<ulong, uint>();
foreach (((uint AppID, Steam.Asset.EType Type) set, Dictionary<ulong, uint> ourFullItems) in fullState.Where(set => !skippedSetsThisUser.Contains(set.Key) && listedUser.MatchableTypes.Contains(set.Key.Type) && set.Value.Values.Any(count => count > 1))) {
foreach (((uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity) set, Dictionary<ulong, uint> ourFullItems) in fullState.Where(set => !skippedSetsThisUser.Contains(set.Key) && listedUser.MatchableTypes.Contains(set.Key.Type) && set.Value.Values.Any(count => count > 1))) {
if (!tradableState.TryGetValue(set, out Dictionary<ulong, uint> ourTradableItems) || (ourTradableItems.Count == 0)) {
continue;
}
@@ -473,7 +479,7 @@ namespace ArchiSteamFarm {
HashSet<Steam.Asset> itemsToGive = Trading.GetTradableItemsFromInventory(ourInventory, classIDsToGive);
HashSet<Steam.Asset> itemsToReceive = Trading.GetTradableItemsFromInventory(theirInventory, classIDsToReceive);
if ((itemsToGive.Count != itemsToReceive.Count) || !Trading.IsFairTypesExchange(itemsToGive, itemsToReceive)) {
if ((itemsToGive.Count != itemsToReceive.Count) || !Trading.IsFairExchange(itemsToGive, itemsToReceive)) {
// Failsafe
Bot.ArchiLogger.LogGenericError(string.Format(Strings.WarningFailedWithError, Strings.ErrorAborted));
@@ -538,7 +544,7 @@ namespace ArchiSteamFarm {
skippedSetsThisRound.UnionWith(skippedSetsThisUser);
foreach ((uint AppID, Steam.Asset.EType Type) skippedSet in skippedSetsThisUser) {
foreach ((uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity) skippedSet in skippedSetsThisUser) {
fullState.Remove(skippedSet);
tradableState.Remove(skippedSet);
}

View File

@@ -20,7 +20,7 @@
// limitations under the License.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using ArchiSteamFarm.Localization;
@@ -55,7 +55,7 @@ namespace ArchiSteamFarm {
Bot.ArchiLogger.LogGenericTrace(Strings.Starting);
for (byte i = 0; (i < MaxSingleQueuesDaily) && (await IsDiscoveryQueueAvailable().ConfigureAwait(false)).GetValueOrDefault(); i++) {
HashSet<uint> queue = await Bot.ArchiWebHandler.GenerateNewDiscoveryQueue().ConfigureAwait(false);
ImmutableHashSet<uint> queue = await Bot.ArchiWebHandler.GenerateNewDiscoveryQueue().ConfigureAwait(false);
if ((queue == null) || (queue.Count == 0)) {
Bot.ArchiLogger.LogGenericTrace(string.Format(Strings.ErrorIsEmpty, nameof(queue)));

View File

@@ -46,50 +46,32 @@ namespace ArchiSteamFarm {
public void Dispose() => TradesSemaphore.Dispose();
[PublicAPI]
public static bool IsFairTypesExchange(IReadOnlyCollection<Steam.Asset> itemsToGive, IReadOnlyCollection<Steam.Asset> itemsToReceive) {
public static bool IsFairExchange(IReadOnlyCollection<Steam.Asset> itemsToGive, IReadOnlyCollection<Steam.Asset> itemsToReceive) {
if ((itemsToGive == null) || (itemsToGive.Count == 0) || (itemsToReceive == null) || (itemsToReceive.Count == 0)) {
ASF.ArchiLogger.LogNullError(nameof(itemsToGive) + " || " + nameof(itemsToReceive));
return false;
}
Dictionary<uint, Dictionary<Steam.Asset.EType, uint>> itemsToGivePerGame = new Dictionary<uint, Dictionary<Steam.Asset.EType, uint>>();
Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), uint> itemsToGiveAmounts = new Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), uint>();
foreach (Steam.Asset item in itemsToGive) {
if (itemsToGivePerGame.TryGetValue(item.RealAppID, out Dictionary<Steam.Asset.EType, uint> itemsPerType)) {
itemsPerType[item.Type] = itemsPerType.TryGetValue(item.Type, out uint amount) ? amount + item.Amount : item.Amount;
} else {
itemsPerType = new Dictionary<Steam.Asset.EType, uint> { [item.Type] = item.Amount };
itemsToGivePerGame[item.RealAppID] = itemsPerType;
}
(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity) key = (item.RealAppID, item.Type, item.Rarity);
itemsToGiveAmounts[key] = itemsToGiveAmounts.TryGetValue(key, out uint amount) ? amount + item.Amount : item.Amount;
}
Dictionary<uint, Dictionary<Steam.Asset.EType, uint>> itemsToReceivePerGame = new Dictionary<uint, Dictionary<Steam.Asset.EType, uint>>();
Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), uint> itemsToReceiveAmounts = new Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), uint>();
foreach (Steam.Asset item in itemsToReceive) {
if (itemsToReceivePerGame.TryGetValue(item.RealAppID, out Dictionary<Steam.Asset.EType, uint> itemsPerType)) {
itemsPerType[item.Type] = itemsPerType.TryGetValue(item.Type, out uint amount) ? amount + item.Amount : item.Amount;
} else {
itemsPerType = new Dictionary<Steam.Asset.EType, uint> { [item.Type] = item.Amount };
itemsToReceivePerGame[item.RealAppID] = itemsPerType;
}
(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity) key = (item.RealAppID, item.Type, item.Rarity);
itemsToReceiveAmounts[key] = itemsToReceiveAmounts.TryGetValue(key, out uint amount) ? amount + item.Amount : item.Amount;
}
// Ensure that amount of items to give is at least amount of items to receive (per game and per type)
foreach ((uint appID, Dictionary<Steam.Asset.EType, uint> itemsPerGame) in itemsToGivePerGame) {
if (!itemsToReceivePerGame.TryGetValue(appID, out Dictionary<Steam.Asset.EType, uint> otherItemsPerType)) {
// Ensure that amount of items to give is at least amount of items to receive (per all fairness factors)
foreach (((uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity) key, uint amountToGive) in itemsToGiveAmounts) {
if (!itemsToReceiveAmounts.TryGetValue(key, out uint amountToReceive) || (amountToGive > amountToReceive)) {
return false;
}
foreach ((Steam.Asset.EType type, uint amount) in itemsPerGame) {
if (!otherItemsPerType.TryGetValue(type, out uint otherAmount)) {
return false;
}
if (amount > otherAmount) {
return false;
}
}
}
return true;
@@ -109,7 +91,7 @@ namespace ArchiSteamFarm {
// All of those cases should be verified by our unit tests to ensure that the logic here matches all possible cases, especially those that were incorrectly handled previously
// Firstly we get initial sets state of our inventory
Dictionary<(uint AppID, Steam.Asset.EType Type), List<uint>> initialSets = GetInventorySets(inventory);
Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), List<uint>> initialSets = GetInventorySets(inventory);
// Once we have initial state, we remove items that we're supposed to give from our inventory
// This loop is a bit more complex due to the fact that we might have a mix of the same item splitted into different amounts
@@ -149,10 +131,10 @@ namespace ArchiSteamFarm {
}
// Now we can get final sets state of our inventory after the exchange
Dictionary<(uint AppID, Steam.Asset.EType Type), List<uint>> finalSets = GetInventorySets(inventory);
Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), List<uint>> finalSets = GetInventorySets(inventory);
// Once we have both states, we can check overall fairness
foreach (((uint AppID, Steam.Asset.EType Type) set, List<uint> beforeAmounts) in initialSets) {
foreach (((uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity) set, List<uint> beforeAmounts) in initialSets) {
if (!finalSets.TryGetValue(set, out List<uint> afterAmounts)) {
// If we have no info about this set, then it has to be a bad one
return false;
@@ -201,18 +183,18 @@ namespace ArchiSteamFarm {
return true;
}
internal static (Dictionary<(uint AppID, Steam.Asset.EType Type), Dictionary<ulong, uint>> FullState, Dictionary<(uint AppID, Steam.Asset.EType Type), Dictionary<ulong, uint>> TradableState) GetDividedInventoryState(IReadOnlyCollection<Steam.Asset> inventory) {
internal static (Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), Dictionary<ulong, uint>> FullState, Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), Dictionary<ulong, uint>> TradableState) GetDividedInventoryState(IReadOnlyCollection<Steam.Asset> inventory) {
if ((inventory == null) || (inventory.Count == 0)) {
ASF.ArchiLogger.LogNullError(nameof(inventory));
return (null, null);
}
Dictionary<(uint AppID, Steam.Asset.EType Type), Dictionary<ulong, uint>> fullState = new Dictionary<(uint AppID, Steam.Asset.EType Type), Dictionary<ulong, uint>>();
Dictionary<(uint AppID, Steam.Asset.EType Type), Dictionary<ulong, uint>> tradableState = new Dictionary<(uint AppID, Steam.Asset.EType Type), Dictionary<ulong, uint>>();
Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), Dictionary<ulong, uint>> fullState = new Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), Dictionary<ulong, uint>>();
Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), Dictionary<ulong, uint>> tradableState = new Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), Dictionary<ulong, uint>>();
foreach (Steam.Asset item in inventory) {
(uint RealAppID, Steam.Asset.EType Type) key = (item.RealAppID, item.Type);
(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity) key = (item.RealAppID, item.Type, item.Rarity);
if (fullState.TryGetValue(key, out Dictionary<ulong, uint> fullSet)) {
if (fullSet.TryGetValue(item.ClassID, out uint amount)) {
@@ -242,17 +224,17 @@ namespace ArchiSteamFarm {
return (fullState, tradableState);
}
internal static Dictionary<(uint AppID, Steam.Asset.EType Type), Dictionary<ulong, uint>> GetInventoryState(IReadOnlyCollection<Steam.Asset> inventory) {
internal static Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), Dictionary<ulong, uint>> GetInventoryState(IReadOnlyCollection<Steam.Asset> inventory) {
if ((inventory == null) || (inventory.Count == 0)) {
ASF.ArchiLogger.LogNullError(nameof(inventory));
return null;
}
Dictionary<(uint AppID, Steam.Asset.EType Type), Dictionary<ulong, uint>> state = new Dictionary<(uint AppID, Steam.Asset.EType Type), Dictionary<ulong, uint>>();
Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), Dictionary<ulong, uint>> state = new Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), Dictionary<ulong, uint>>();
foreach (Steam.Asset item in inventory) {
(uint RealAppID, Steam.Asset.EType Type) key = (item.RealAppID, item.Type);
(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity) key = (item.RealAppID, item.Type, item.Rarity);
if (state.TryGetValue(key, out Dictionary<ulong, uint> set)) {
if (set.TryGetValue(item.ClassID, out uint amount)) {
@@ -298,14 +280,14 @@ namespace ArchiSteamFarm {
return result;
}
internal static bool IsEmptyForMatching(IReadOnlyDictionary<(uint AppID, Steam.Asset.EType Type), Dictionary<ulong, uint>> fullState, IReadOnlyDictionary<(uint AppID, Steam.Asset.EType Type), Dictionary<ulong, uint>> tradableState) {
internal static bool IsEmptyForMatching(IReadOnlyDictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), Dictionary<ulong, uint>> fullState, IReadOnlyDictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), Dictionary<ulong, uint>> tradableState) {
if ((fullState == null) || (tradableState == null)) {
ASF.ArchiLogger.LogNullError(nameof(fullState) + " || " + nameof(tradableState));
return false;
}
foreach (((uint AppID, Steam.Asset.EType Type) set, Dictionary<ulong, uint> state) in tradableState) {
foreach (((uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity) set, Dictionary<ulong, uint> state) in tradableState) {
if (!fullState.TryGetValue(set, out Dictionary<ulong, uint> fullSet) || (fullSet == null) || (fullSet.Count == 0)) {
ASF.ArchiLogger.LogNullError(nameof(fullSet));
@@ -391,14 +373,14 @@ namespace ArchiSteamFarm {
}
}
private static Dictionary<(uint AppID, Steam.Asset.EType Type), List<uint>> GetInventorySets(IReadOnlyCollection<Steam.Asset> inventory) {
private static Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), List<uint>> GetInventorySets(IReadOnlyCollection<Steam.Asset> inventory) {
if ((inventory == null) || (inventory.Count == 0)) {
ASF.ArchiLogger.LogNullError(nameof(inventory));
return null;
}
Dictionary<(uint AppID, Steam.Asset.EType Type), Dictionary<ulong, uint>> sets = GetInventoryState(inventory);
Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), Dictionary<ulong, uint>> sets = GetInventoryState(inventory);
return sets.ToDictionary(set => set.Key, set => set.Value.Values.OrderBy(amount => amount).ToList());
}
@@ -582,7 +564,7 @@ namespace ArchiSteamFarm {
}
// Decline trade if we're requested to handle any not-accepted item type or if it's not fair games/types exchange
if (!tradeOffer.IsValidSteamItemsRequest(Bot.BotConfig.MatchableTypes) || !IsFairTypesExchange(tradeOffer.ItemsToGive, tradeOffer.ItemsToReceive)) {
if (!tradeOffer.IsValidSteamItemsRequest(Bot.BotConfig.MatchableTypes) || !IsFairExchange(tradeOffer.ItemsToGive, tradeOffer.ItemsToReceive)) {
return new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.Rejected, tradeOffer.ItemsToReceive);
}
@@ -610,10 +592,10 @@ namespace ArchiSteamFarm {
}
// Get sets we're interested in
HashSet<(uint AppID, Steam.Asset.EType Type)> wantedSets = new HashSet<(uint AppID, Steam.Asset.EType Type)>();
HashSet<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity)> wantedSets = new HashSet<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity)>();
foreach (Steam.Asset item in tradeOffer.ItemsToGive) {
wantedSets.Add((item.RealAppID, item.Type));
wantedSets.Add((item.RealAppID, item.Type, item.Rarity));
}
// Now check if it's worth for us to do the trade

2
wiki

Submodule wiki updated: 92db2be904...22e3db69e6