Compare commits

...

16 Commits

Author SHA1 Message Date
dependabot[bot]
af8c48adce Bump ASF-WebConfigGenerator from adb2111 to 249bf61 (#1077)
Bumps [ASF-WebConfigGenerator](https://github.com/JustArchiNET/ASF-WebConfigGenerator) from `adb2111` to `249bf61`.
- [Release notes](https://github.com/JustArchiNET/ASF-WebConfigGenerator/releases)
- [Commits](adb2111205...249bf61ca7)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-02-05 03:24:18 +01:00
dependabot[bot]
624a0d9fca Bump ASF-ui from 9aa6d5c to ced8221
Bumps [ASF-ui](https://github.com/JustArchiNET/ASF-ui) from `9aa6d5c` to `ced8221`.
- [Release notes](https://github.com/JustArchiNET/ASF-ui/releases)
- [Commits](9aa6d5cd28...ced8221b21)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-02-05 01:41:42 +00:00
dependabot[bot]
68e09e786b Bump wiki from 22e3db6 to 12ba9ed
Bumps [wiki](https://github.com/JustArchiNET/ArchiSteamFarm.wiki) from `22e3db6` to `12ba9ed`.
- [Release notes](https://github.com/JustArchiNET/ArchiSteamFarm.wiki/releases)
- [Commits](22e3db69e6...12ba9edb75)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-02-05 01:34:17 +00:00
JustArchi
922f572383 Add handling for consumable EType 2019-02-04 22:29:00 +01:00
JustArchi
f19d821388 Add ProfileModifier EType 2019-02-04 22:08:50 +01:00
JustArchi
a3f600efd9 Correct returned version for update function 2019-02-04 22:06:26 +01:00
JustArchi
5cf07ca691 Bump 2019-02-04 03:21:38 +01:00
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
17 changed files with 339 additions and 203 deletions

2
ASF-ui

Submodule ASF-ui updated: 6e328457f5...ced8221b21

View File

@@ -36,7 +36,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="ConfigureAwaitChecker.Analyzer" Version="3.0.0" /> <PackageReference Include="ConfigureAwaitChecker.Analyzer" Version="3.0.0" />
<PackageReference Include="JetBrains.Annotations" Version="2018.3.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.TestAdapter" Version="1.4.0" />
<PackageReference Include="MSTest.TestFramework" Version="1.4.0" /> <PackageReference Include="MSTest.TestFramework" Version="1.4.0" />
</ItemGroup> </ItemGroup>

View File

@@ -28,6 +28,30 @@ using static ArchiSteamFarm.Trading;
namespace ArchiSteamFarm.Tests { namespace ArchiSteamFarm.Tests {
[TestClass] [TestClass]
public sealed class Trading { 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] [TestMethod]
public void MultiGameMultiTypeBadReject() { public void MultiGameMultiTypeBadReject() {
HashSet<Steam.Asset> inventory = new HashSet<Steam.Asset> { HashSet<Steam.Asset> inventory = new HashSet<Steam.Asset> {
@@ -46,6 +70,7 @@ namespace ArchiSteamFarm.Tests {
CreateItem(3, realAppID: 730, type: Steam.Asset.EType.Emoticon) CreateItem(3, realAppID: 730, type: Steam.Asset.EType.Emoticon)
}; };
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive)); Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
} }
@@ -66,6 +91,7 @@ namespace ArchiSteamFarm.Tests {
CreateItem(4, realAppID: 730, type: Steam.Asset.EType.Emoticon) CreateItem(4, realAppID: 730, type: Steam.Asset.EType.Emoticon)
}; };
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive)); Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
} }
@@ -87,6 +113,7 @@ namespace ArchiSteamFarm.Tests {
CreateItem(4, realAppID: 730) CreateItem(4, realAppID: 730)
}; };
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive)); Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
} }
@@ -107,6 +134,7 @@ namespace ArchiSteamFarm.Tests {
CreateItem(4, realAppID: 730) CreateItem(4, realAppID: 730)
}; };
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive)); Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
} }
@@ -125,6 +153,7 @@ namespace ArchiSteamFarm.Tests {
CreateItem(3, type: Steam.Asset.EType.SteamGems) CreateItem(3, type: Steam.Asset.EType.SteamGems)
}; };
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive)); Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
} }
@@ -146,6 +175,7 @@ namespace ArchiSteamFarm.Tests {
CreateItem(3, type: Steam.Asset.EType.Emoticon) CreateItem(3, type: Steam.Asset.EType.Emoticon)
}; };
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive)); Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
} }
@@ -166,6 +196,7 @@ namespace ArchiSteamFarm.Tests {
CreateItem(4, type: Steam.Asset.EType.Emoticon) CreateItem(4, type: Steam.Asset.EType.Emoticon)
}; };
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, 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) }; HashSet<Steam.Asset> itemsToReceive = new HashSet<Steam.Asset> { CreateItem(4, 3) };
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, 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) }; HashSet<Steam.Asset> itemsToReceive = new HashSet<Steam.Asset> { CreateItem(3, 3) };
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, 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) }; HashSet<Steam.Asset> itemsToReceive = new HashSet<Steam.Asset> { CreateItem(3, 2) };
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, 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> itemsToGive = new HashSet<Steam.Asset> { CreateItem(1) };
HashSet<Steam.Asset> itemsToReceive = new HashSet<Steam.Asset> { CreateItem(2) }; HashSet<Steam.Asset> itemsToReceive = new HashSet<Steam.Asset> { CreateItem(2) };
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive)); Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
} }
@@ -250,6 +285,7 @@ namespace ArchiSteamFarm.Tests {
CreateItem(3) CreateItem(3)
}; };
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, 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> itemsToGive = new HashSet<Steam.Asset> { CreateItem(2) };
HashSet<Steam.Asset> itemsToReceive = new HashSet<Steam.Asset> { CreateItem(3) }; HashSet<Steam.Asset> itemsToReceive = new HashSet<Steam.Asset> { CreateItem(3) };
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive)); Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
} }
@@ -287,6 +324,7 @@ namespace ArchiSteamFarm.Tests {
CreateItem(4) CreateItem(4)
}; };
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, 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> itemsToGive = new HashSet<Steam.Asset> { CreateItem(1) };
HashSet<Steam.Asset> itemsToReceive = new HashSet<Steam.Asset> { CreateItem(2) }; HashSet<Steam.Asset> itemsToReceive = new HashSet<Steam.Asset> { CreateItem(2) };
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, 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> itemsToGive = new HashSet<Steam.Asset> { CreateItem(1) };
HashSet<Steam.Asset> itemsToReceive = new HashSet<Steam.Asset> { CreateItem(2) }; HashSet<Steam.Asset> itemsToReceive = new HashSet<Steam.Asset> { CreateItem(2) };
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive)); Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
} }
@@ -322,10 +362,11 @@ namespace ArchiSteamFarm.Tests {
CreateItem(3) CreateItem(3)
}; };
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive)); Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
} }
[NotNull] [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

@@ -178,15 +178,13 @@ namespace ArchiSteamFarm {
ArchiLogger.LogGenericInfo(string.Format(Strings.UpdateVersionInfo, SharedInfo.Version, newVersion)); ArchiLogger.LogGenericInfo(string.Format(Strings.UpdateVersionInfo, SharedInfo.Version, newVersion));
if (SharedInfo.Version == newVersion) { if (SharedInfo.Version >= newVersion) {
return SharedInfo.Version; if (SharedInfo.Version > newVersion) {
} ArchiLogger.LogGenericWarning(Strings.WarningPreReleaseVersion);
await Task.Delay(15 * 1000).ConfigureAwait(false);
}
if (SharedInfo.Version > newVersion) { return newVersion;
ArchiLogger.LogGenericWarning(Strings.WarningPreReleaseVersion);
await Task.Delay(15 * 1000).ConfigureAwait(false);
return SharedInfo.Version;
} }
if (!updateOverride && (GlobalConfig.UpdatePeriod == 0)) { if (!updateOverride && (GlobalConfig.UpdatePeriod == 0)) {

View File

@@ -250,7 +250,7 @@ namespace ArchiSteamFarm {
TradingScheduled = false; 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)) { if ((inventory == null) || (inventory.Count == 0)) {
return (false, string.Format(Strings.ErrorIsEmpty, nameof(inventory))); return (false, string.Format(Strings.ErrorIsEmpty, nameof(inventory)));

View File

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

View File

@@ -119,7 +119,7 @@ namespace ArchiSteamFarm {
[ItemCanBeNull] [ItemCanBeNull]
[PublicAPI] [PublicAPI]
[SuppressMessage("ReSharper", "FunctionComplexityOverflow")] [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)) { if ((appID == 0) || (contextID == 0)) {
Bot.ArchiLogger.LogNullError(nameof(appID) + " || " + nameof(contextID)); Bot.ArchiLogger.LogNullError(nameof(appID) + " || " + nameof(contextID));
@@ -174,7 +174,7 @@ namespace ArchiSteamFarm {
return null; 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)) { foreach (Steam.InventoryResponse.Description description in response.Descriptions.Where(description => description != null)) {
if (description.ClassID == 0) { if (description.ClassID == 0) {
@@ -187,25 +187,14 @@ namespace ArchiSteamFarm {
continue; continue;
} }
uint realAppID = 0; uint realAppID = description.RealAppID > 0 ? description.RealAppID : description.AppID;
Steam.Asset.EType type = Steam.Asset.EType.Unknown;
if (appID == Steam.Asset.SteamAppID) { descriptions[description.ClassID] = (description.Marketable, description.Tradable, realAppID, description.Type, description.Rarity);
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);
} }
foreach (Steam.Asset asset in response.Assets.Where(asset => asset != null)) { 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 (descriptions.TryGetValue(asset.ClassID, out (bool Marketable, bool Tradable, uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity) 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 ((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; continue;
} }
@@ -213,7 +202,8 @@ namespace ArchiSteamFarm {
asset.Tradable = description.Tradable; asset.Tradable = description.Tradable;
asset.RealAppID = description.RealAppID; asset.RealAppID = description.RealAppID;
asset.Type = description.Type; 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; continue;
} }
@@ -1145,7 +1135,7 @@ namespace ArchiSteamFarm {
internal HttpClient GenerateDisposableHttpClient() => WebBrowser.GenerateDisposableHttpClient(); internal HttpClient GenerateDisposableHttpClient() => WebBrowser.GenerateDisposableHttpClient();
[ItemCanBeNull] [ItemCanBeNull]
internal async Task<HashSet<uint>> GenerateNewDiscoveryQueue() { internal async Task<ImmutableHashSet<uint>> GenerateNewDiscoveryQueue() {
const string request = "/explore/generatenewdiscoveryqueue"; const string request = "/explore/generatenewdiscoveryqueue";
// Extra entry for sessionID // Extra entry for sessionID
@@ -1196,7 +1186,7 @@ namespace ArchiSteamFarm {
return null; 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) { foreach (KeyValue description in response["descriptions"].Children) {
uint appID = description["appid"].AsUnsignedInteger(); uint appID = description["appid"].AsUnsignedInteger();
@@ -1221,24 +1211,51 @@ namespace ArchiSteamFarm {
bool marketable = description["marketable"].AsBoolean(); bool marketable = description["marketable"].AsBoolean();
uint realAppID = 0; uint realAppID = appID;
Steam.Asset.EType type = Steam.Asset.EType.Unknown; KeyValue marketFeeApp = description["market_fee_app"];
if (appID == Steam.Asset.SteamAppID) { if (marketFeeApp != KeyValue.Invalid) {
string hashName = description["market_hash_name"].Value; realAppID = description["market_fee_app"].AsUnsignedInteger();
if (!string.IsNullOrEmpty(hashName)) { if (realAppID == 0) {
realAppID = GetAppIDFromMarketHashName(hashName); Bot.ArchiLogger.LogNullError(nameof(realAppID));
}
string descriptionType = description["type"].Value; return null;
if (!string.IsNullOrEmpty(descriptionType)) {
type = GetItemType(descriptionType);
} }
} }
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>(); HashSet<Steam.TradeOffer> result = new HashSet<Steam.TradeOffer>();
@@ -1612,7 +1629,7 @@ namespace ArchiSteamFarm {
return null; return null;
} }
result[appID] = game["name"].Value; result[appID] = game["name"].AsString();
} }
return result; return result;
@@ -1911,7 +1928,7 @@ namespace ArchiSteamFarm {
return false; return false;
} }
string steamLogin = response["token"].Value; string steamLogin = response["token"].AsString();
if (string.IsNullOrEmpty(steamLogin)) { if (string.IsNullOrEmpty(steamLogin)) {
Bot.ArchiLogger.LogNullError(nameof(steamLogin)); Bot.ArchiLogger.LogNullError(nameof(steamLogin));
@@ -1919,7 +1936,7 @@ namespace ArchiSteamFarm {
return false; return false;
} }
string steamLoginSecure = response["tokensecure"].Value; string steamLoginSecure = response["tokensecure"].AsString();
if (string.IsNullOrEmpty(steamLoginSecure)) { if (string.IsNullOrEmpty(steamLoginSecure)) {
Bot.ArchiLogger.LogNullError(nameof(steamLoginSecure)); Bot.ArchiLogger.LogNullError(nameof(steamLoginSecure));
@@ -2241,76 +2258,6 @@ namespace ArchiSteamFarm {
return (ESteamApiKeyState.Registered, text); 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) { private async Task<bool> IsProfileUri(Uri uri, bool waitForInitialization = true) {
if (uri == null) { if (uri == null) {
ASF.ArchiLogger.LogNullError(nameof(uri)); ASF.ArchiLogger.LogNullError(nameof(uri));
@@ -2383,7 +2330,7 @@ namespace ArchiSteamFarm {
return uri.AbsolutePath.StartsWith("/login", StringComparison.Ordinal) || uri.Host.Equals("lostauth"); 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)) { if ((descriptions == null) || (input == null) || (input.Count == 0) || (output == null)) {
ASF.ArchiLogger.LogNullError(nameof(descriptions) + " || " + nameof(input) + " || " + nameof(output)); ASF.ArchiLogger.LogNullError(nameof(descriptions) + " || " + nameof(input) + " || " + nameof(output));
@@ -2426,14 +2373,16 @@ namespace ArchiSteamFarm {
bool marketable = true; bool marketable = true;
uint realAppID = 0; uint realAppID = 0;
Steam.Asset.EType type = Steam.Asset.EType.Unknown; 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; marketable = description.Marketable;
realAppID = description.RealAppID; realAppID = description.RealAppID;
type = description.Type; 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); output.Add(steamAsset);
} }

View File

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

View File

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

View File

@@ -257,6 +257,10 @@ namespace ArchiSteamFarm {
return (false, string.Format(Strings.ErrorConfigPropertyInvalid, nameof(SteamMessagePrefix), SteamMessagePrefix)); 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)) { if ((SteamProtocols <= 0) || (SteamProtocols > ProtocolTypes.All)) {
return (false, string.Format(Strings.ErrorConfigPropertyInvalid, nameof(SteamProtocols), SteamProtocols)); return (false, string.Format(Strings.ErrorConfigPropertyInvalid, nameof(SteamProtocols), SteamProtocols));
} }

View File

@@ -21,6 +21,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; 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)))); 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)) { if ((response == null) || (response.Count == 0)) {
return BadRequest(new GenericResponse<IReadOnlyCollection<GitHub.ReleaseResponse>>(false, string.Format(Strings.ErrorRequestFailedTooManyTimes, WebBrowser.MaxTries))); return BadRequest(new GenericResponse<IReadOnlyCollection<GitHub.ReleaseResponse>>(false, string.Format(Strings.ErrorRequestFailedTooManyTimes, WebBrowser.MaxTries)));

View File

@@ -21,6 +21,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using ArchiSteamFarm.Localization; using ArchiSteamFarm.Localization;
@@ -57,6 +58,9 @@ namespace ArchiSteamFarm.Json {
[PublicAPI] [PublicAPI]
public bool Marketable { get; internal set; } public bool Marketable { get; internal set; }
[PublicAPI]
public ERarity Rarity { get; internal set; }
[PublicAPI] [PublicAPI]
public uint RealAppID { get; internal set; } public uint RealAppID { get; internal set; }
@@ -160,7 +164,7 @@ namespace ArchiSteamFarm.Json {
} }
// Constructed from trades being received or plugins // 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)) { if ((appID == 0) || (contextID == 0) || (classID == 0) || (amount == 0)) {
throw new ArgumentNullException(nameof(appID) + " || " + nameof(contextID) + " || " + nameof(classID) + " || " + nameof(amount)); throw new ArgumentNullException(nameof(appID) + " || " + nameof(contextID) + " || " + nameof(classID) + " || " + nameof(amount));
} }
@@ -172,11 +176,19 @@ namespace ArchiSteamFarm.Json {
Marketable = marketable; Marketable = marketable;
RealAppID = realAppID; RealAppID = realAppID;
Type = type; Type = type;
Rarity = rarity;
} }
[JsonConstructor] [JsonConstructor]
private Asset() { } private Asset() { }
public enum ERarity : byte {
Unknown,
Common,
Uncommon,
Rare
}
public enum EType : byte { public enum EType : byte {
Unknown, Unknown,
BoosterPack, BoosterPack,
@@ -186,7 +198,8 @@ namespace ArchiSteamFarm.Json {
TradingCard, TradingCard,
SteamGems, SteamGems,
SaleItem, SaleItem,
Consumable Consumable,
ProfileModifier
} }
} }
@@ -391,10 +404,10 @@ namespace ArchiSteamFarm.Json {
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] [SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
internal sealed class InventoryResponse : NumberResponse { internal sealed class InventoryResponse : NumberResponse {
[JsonProperty(PropertyName = "assets", Required = Required.DisallowNull)] [JsonProperty(PropertyName = "assets", Required = Required.DisallowNull)]
internal readonly HashSet<Asset> Assets; internal readonly ImmutableHashSet<Asset> Assets;
[JsonProperty(PropertyName = "descriptions", Required = Required.DisallowNull)] [JsonProperty(PropertyName = "descriptions", Required = Required.DisallowNull)]
internal readonly HashSet<Description> Descriptions; internal readonly ImmutableHashSet<Description> Descriptions;
[JsonProperty(PropertyName = "error", Required = Required.DisallowNull)] [JsonProperty(PropertyName = "error", Required = Required.DisallowNull)]
internal readonly string Error; internal readonly string Error;
@@ -436,15 +449,14 @@ namespace ArchiSteamFarm.Json {
[JsonProperty(PropertyName = "appid", Required = Required.Always)] [JsonProperty(PropertyName = "appid", Required = Required.Always)]
internal readonly uint AppID; internal readonly uint AppID;
[JsonProperty(PropertyName = "market_hash_name", Required = Required.Always)] [JsonProperty(PropertyName = "market_fee_app", Required = Required.DisallowNull)]
internal readonly string MarketHashName; internal readonly uint RealAppID;
[JsonProperty(PropertyName = "type", Required = Required.Always)]
internal readonly string Type;
internal ulong ClassID { get; private set; } internal ulong ClassID { get; private set; }
internal bool Marketable { get; private set; } internal bool Marketable { get; private set; }
internal Asset.ERarity Rarity { get; private set; }
internal bool Tradable { get; private set; } internal bool Tradable { get; private set; }
internal Asset.EType Type { get; private set; }
[JsonProperty(PropertyName = "classid", Required = Required.Always)] [JsonProperty(PropertyName = "classid", Required = Required.Always)]
private string ClassIDText { private string ClassIDText {
@@ -470,6 +482,19 @@ namespace ArchiSteamFarm.Json {
set => Marketable = value > 0; 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)] [JsonProperty(PropertyName = "tradable", Required = Required.Always)]
private byte TradableNumber { private byte TradableNumber {
set => Tradable = value > 0; set => Tradable = value > 0;
@@ -477,13 +502,137 @@ namespace ArchiSteamFarm.Json {
[JsonConstructor] [JsonConstructor]
private Description() { } 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_6":
type = Asset.EType.Consumable;
break;
case "item_class_7":
type = Asset.EType.SteamGems;
break;
case "item_class_8":
type = Asset.EType.ProfileModifier;
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")] [SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
internal sealed class NewDiscoveryQueueResponse { internal sealed class NewDiscoveryQueueResponse {
[JsonProperty(PropertyName = "queue", Required = Required.Always)] [JsonProperty(PropertyName = "queue", Required = Required.Always)]
internal readonly HashSet<uint> Queue; internal readonly ImmutableHashSet<uint> Queue;
[JsonConstructor] [JsonConstructor]
private NewDiscoveryQueueResponse() { } private NewDiscoveryQueueResponse() { }

View File

@@ -184,10 +184,10 @@ namespace ArchiSteamFarm {
} }
[ItemCanBeNull] [ItemCanBeNull]
private async Task<HashSet<ListedUser>> GetListedUsers() { private async Task<ImmutableHashSet<ListedUser>> GetListedUsers() {
const string request = URL + "/Api/Bots"; 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; return objectResponse?.Content;
} }
@@ -300,7 +300,7 @@ namespace ArchiSteamFarm {
return false; 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)) { if (Trading.IsEmptyForMatching(fullState, tradableState)) {
// User doesn't have any more dupes in the inventory // User doesn't have any more dupes in the inventory
@@ -309,7 +309,7 @@ namespace ArchiSteamFarm {
return false; return false;
} }
HashSet<ListedUser> listedUsers = await GetListedUsers().ConfigureAwait(false); ImmutableHashSet<ListedUser> listedUsers = await GetListedUsers().ConfigureAwait(false);
if ((listedUsers == null) || (listedUsers.Count == 0)) { if ((listedUsers == null) || (listedUsers.Count == 0)) {
Bot.ArchiLogger.LogGenericTrace(string.Format(Strings.ErrorIsEmpty, nameof(listedUsers))); Bot.ArchiLogger.LogGenericTrace(string.Format(Strings.ErrorIsEmpty, nameof(listedUsers)));
@@ -318,12 +318,18 @@ namespace ArchiSteamFarm {
} }
byte emptyMatches = 0; 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)) { 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 + "..."); 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)) { if ((theirInventory == null) || (theirInventory.Count == 0)) {
Bot.ArchiLogger.LogGenericTrace(string.Format(Strings.ErrorIsEmpty, nameof(theirInventory))); Bot.ArchiLogger.LogGenericTrace(string.Format(Strings.ErrorIsEmpty, nameof(theirInventory)));
@@ -331,19 +337,19 @@ namespace ArchiSteamFarm {
continue; 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 RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), 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>> inventoryStateChanges = new Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), Dictionary<ulong, uint>>();
for (byte i = 0; i < Trading.MaxTradesPerAccount; i++) { for (byte i = 0; i < Trading.MaxTradesPerAccount; i++) {
byte itemsInTrade = 0; 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> classIDsToGive = new Dictionary<ulong, uint>();
Dictionary<ulong, uint> classIDsToReceive = 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)) { if (!tradableState.TryGetValue(set, out Dictionary<ulong, uint> ourTradableItems) || (ourTradableItems.Count == 0)) {
continue; continue;
} }
@@ -473,7 +479,7 @@ namespace ArchiSteamFarm {
HashSet<Steam.Asset> itemsToGive = Trading.GetTradableItemsFromInventory(ourInventory, classIDsToGive); HashSet<Steam.Asset> itemsToGive = Trading.GetTradableItemsFromInventory(ourInventory, classIDsToGive);
HashSet<Steam.Asset> itemsToReceive = Trading.GetTradableItemsFromInventory(theirInventory, classIDsToReceive); 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 // Failsafe
Bot.ArchiLogger.LogGenericError(string.Format(Strings.WarningFailedWithError, Strings.ErrorAborted)); Bot.ArchiLogger.LogGenericError(string.Format(Strings.WarningFailedWithError, Strings.ErrorAborted));
@@ -538,7 +544,7 @@ namespace ArchiSteamFarm {
skippedSetsThisRound.UnionWith(skippedSetsThisUser); 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); fullState.Remove(skippedSet);
tradableState.Remove(skippedSet); tradableState.Remove(skippedSet);
} }

View File

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

View File

@@ -46,50 +46,32 @@ namespace ArchiSteamFarm {
public void Dispose() => TradesSemaphore.Dispose(); public void Dispose() => TradesSemaphore.Dispose();
[PublicAPI] [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)) { if ((itemsToGive == null) || (itemsToGive.Count == 0) || (itemsToReceive == null) || (itemsToReceive.Count == 0)) {
ASF.ArchiLogger.LogNullError(nameof(itemsToGive) + " || " + nameof(itemsToReceive)); ASF.ArchiLogger.LogNullError(nameof(itemsToGive) + " || " + nameof(itemsToReceive));
return false; 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) { foreach (Steam.Asset item in itemsToGive) {
if (itemsToGivePerGame.TryGetValue(item.RealAppID, out Dictionary<Steam.Asset.EType, uint> itemsPerType)) { (uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity) key = (item.RealAppID, item.Type, item.Rarity);
itemsPerType[item.Type] = itemsPerType.TryGetValue(item.Type, out uint amount) ? amount + item.Amount : item.Amount; itemsToGiveAmounts[key] = itemsToGiveAmounts.TryGetValue(key, out uint amount) ? amount + item.Amount : item.Amount;
} else {
itemsPerType = new Dictionary<Steam.Asset.EType, uint> { [item.Type] = item.Amount };
itemsToGivePerGame[item.RealAppID] = itemsPerType;
}
} }
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) { foreach (Steam.Asset item in itemsToReceive) {
if (itemsToReceivePerGame.TryGetValue(item.RealAppID, out Dictionary<Steam.Asset.EType, uint> itemsPerType)) { (uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity) key = (item.RealAppID, item.Type, item.Rarity);
itemsPerType[item.Type] = itemsPerType.TryGetValue(item.Type, out uint amount) ? amount + item.Amount : item.Amount; itemsToReceiveAmounts[key] = itemsToReceiveAmounts.TryGetValue(key, out uint amount) ? amount + item.Amount : item.Amount;
} else {
itemsPerType = new Dictionary<Steam.Asset.EType, uint> { [item.Type] = item.Amount };
itemsToReceivePerGame[item.RealAppID] = itemsPerType;
}
} }
// Ensure that amount of items to give is at least amount of items to receive (per game and per type) // Ensure that amount of items to give is at least amount of items to receive (per all fairness factors)
foreach ((uint appID, Dictionary<Steam.Asset.EType, uint> itemsPerGame) in itemsToGivePerGame) { foreach (((uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity) key, uint amountToGive) in itemsToGiveAmounts) {
if (!itemsToReceivePerGame.TryGetValue(appID, out Dictionary<Steam.Asset.EType, uint> otherItemsPerType)) { if (!itemsToReceiveAmounts.TryGetValue(key, out uint amountToReceive) || (amountToGive > amountToReceive)) {
return false; 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; 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 // 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 // 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 // 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 // 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 // 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 // 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 (!finalSets.TryGetValue(set, out List<uint> afterAmounts)) {
// If we have no info about this set, then it has to be a bad one // If we have no info about this set, then it has to be a bad one
return false; return false;
@@ -201,18 +183,18 @@ namespace ArchiSteamFarm {
return true; 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)) { if ((inventory == null) || (inventory.Count == 0)) {
ASF.ArchiLogger.LogNullError(nameof(inventory)); ASF.ArchiLogger.LogNullError(nameof(inventory));
return (null, null); 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 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 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>> tradableState = new Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), Dictionary<ulong, uint>>();
foreach (Steam.Asset item in inventory) { 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 (fullState.TryGetValue(key, out Dictionary<ulong, uint> fullSet)) {
if (fullSet.TryGetValue(item.ClassID, out uint amount)) { if (fullSet.TryGetValue(item.ClassID, out uint amount)) {
@@ -242,17 +224,17 @@ namespace ArchiSteamFarm {
return (fullState, tradableState); 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)) { if ((inventory == null) || (inventory.Count == 0)) {
ASF.ArchiLogger.LogNullError(nameof(inventory)); ASF.ArchiLogger.LogNullError(nameof(inventory));
return null; 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) { 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 (state.TryGetValue(key, out Dictionary<ulong, uint> set)) {
if (set.TryGetValue(item.ClassID, out uint amount)) { if (set.TryGetValue(item.ClassID, out uint amount)) {
@@ -298,14 +280,14 @@ namespace ArchiSteamFarm {
return result; 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)) { if ((fullState == null) || (tradableState == null)) {
ASF.ArchiLogger.LogNullError(nameof(fullState) + " || " + nameof(tradableState)); ASF.ArchiLogger.LogNullError(nameof(fullState) + " || " + nameof(tradableState));
return false; 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)) { if (!fullState.TryGetValue(set, out Dictionary<ulong, uint> fullSet) || (fullSet == null) || (fullSet.Count == 0)) {
ASF.ArchiLogger.LogNullError(nameof(fullSet)); 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)) { if ((inventory == null) || (inventory.Count == 0)) {
ASF.ArchiLogger.LogNullError(nameof(inventory)); ASF.ArchiLogger.LogNullError(nameof(inventory));
return null; 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()); 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 // 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); return new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.Rejected, tradeOffer.ItemsToReceive);
} }
@@ -610,10 +592,10 @@ namespace ArchiSteamFarm {
} }
// Get sets we're interested in // 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) { 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 // Now check if it's worth for us to do the trade

2
wiki

Submodule wiki updated: 92db2be904...12ba9edb75