Compare commits

...

28 Commits

Author SHA1 Message Date
JustArchi
32648ac010 Packages update 2019-02-10 02:26:51 +01:00
dependabot[bot]
a7424702c9 Bump ASF-WebConfigGenerator from 0fdd864 to 29cbb19
Bumps [ASF-WebConfigGenerator](https://github.com/JustArchiNET/ASF-WebConfigGenerator) from `0fdd864` to `29cbb19`.
- [Release notes](https://github.com/JustArchiNET/ASF-WebConfigGenerator/releases)
- [Commits](0fdd8645e8...29cbb1923d)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-02-08 01:25:49 +00:00
Łukasz Domeradzki
8b4cee21fd Misc 2019-02-07 17:50:43 +01:00
dependabot[bot]
4deaf0a17a Bump ASF-ui from 272548c to dc5df32
Bumps [ASF-ui](https://github.com/JustArchiNET/ASF-ui) from `272548c` to `dc5df32`.
- [Release notes](https://github.com/JustArchiNET/ASF-ui/releases)
- [Commits](272548ce32...dc5df326aa)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-02-07 01:39:38 +00:00
dependabot[bot]
205f995667 Bump wiki from 32ce54e to b2825ec
Bumps [wiki](https://github.com/JustArchiNET/ArchiSteamFarm.wiki) from `32ce54e` to `b2825ec`.
- [Release notes](https://github.com/JustArchiNET/ArchiSteamFarm.wiki/releases)
- [Commits](32ce54ea06...b2825eccc5)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-02-07 01:39:13 +00:00
dependabot[bot]
cb6f7d5b2e Bump ASF-WebConfigGenerator from 3a40a65 to 0fdd864
Bumps [ASF-WebConfigGenerator](https://github.com/JustArchiNET/ASF-WebConfigGenerator) from `3a40a65` to `0fdd864`.
- [Release notes](https://github.com/JustArchiNET/ASF-WebConfigGenerator/releases)
- [Commits](3a40a65231...0fdd8645e8)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-02-07 01:26:20 +00:00
dependabot[bot]
b1b0677ed7 Bump wiki from 95b4b31 to 32ce54e
Bumps [wiki](https://github.com/JustArchiNET/ArchiSteamFarm.wiki) from `95b4b31` to `32ce54e`.
- [Release notes](https://github.com/JustArchiNET/ArchiSteamFarm.wiki/releases)
- [Commits](95b4b31e6d...32ce54ea06)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-02-06 17:23:13 +00:00
dependabot[bot]
002b8a05e8 Bump ASF-WebConfigGenerator from 286406a to 3a40a65
Bumps [ASF-WebConfigGenerator](https://github.com/JustArchiNET/ASF-WebConfigGenerator) from `286406a` to `3a40a65`.
- [Release notes](https://github.com/JustArchiNET/ASF-WebConfigGenerator/releases)
- [Commits](286406ad2c...3a40a65231)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-02-06 01:27:03 +00:00
JustArchi
76cf9ccb08 Bump 2019-02-05 16:10:31 +01:00
JustArchi
737a58f9ca Translations update 2019-02-05 15:30:23 +01:00
JustArchi
9ab6c48659 Correct RealAppID
We can't use market_fee_app here, as that one gives us *too* real appID (game one), while we want the middle ground (event or game the item is from)
2019-02-05 15:12:15 +01:00
JustArchi
acb9e00274 Bump 2019-02-05 03:29:12 +01:00
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
25 changed files with 1079 additions and 8632 deletions

4
.gitmodules vendored
View File

@@ -1,9 +1,9 @@
[submodule "ASF-WebConfigGenerator"]
path = ASF-WebConfigGenerator
url = https://github.com/JustArchiNET/ASF-WebConfigGenerator
url = https://github.com/JustArchiNET/ASF-WebConfigGenerator.git
[submodule "ASF-ui"]
path = ASF-ui
url = https://github.com/JustArchiNET/ASF-ui
url = https://github.com/JustArchiNET/ASF-ui.git
[submodule "tools/ArchiCrowdin"]
path = tools/ArchiCrowdin
url = https://github.com/JustArchiNET/ArchiCrowdin.git

2
ASF-ui

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

View File

@@ -35,8 +35,7 @@
<ItemGroup>
<PackageReference Include="ConfigureAwaitChecker.Analyzer" Version="3.0.0" />
<PackageReference Include="JetBrains.Annotations" Version="2018.3.0" />
<PackageReference Include="SteamKit2" Version="2.1.0" />
<PackageReference Include="System.Composition.AttributedModel" Version="1.3.0-preview.18571.3" />
<PackageReference Include="System.Composition.AttributedModel" Version="1.3.0-preview.19073.11" />
</ItemGroup>
<ItemGroup>

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

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

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.6</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.6</FileVersion>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<LangVersion>latest</LangVersion>
<NoWarn>1591</NoWarn>
@@ -68,25 +68,24 @@
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="2.2.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.0.0-preview.18572.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Configuration" Version="3.0.0-preview.18572.1" />
<PackageReference Include="Microsoft.Win32.Registry" Version="4.6.0-preview.18571.3" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.0.0-preview.19074.2" />
<PackageReference Include="Microsoft.Extensions.Logging.Configuration" Version="3.0.0-preview.19074.2" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
<PackageReference Include="NLog" Version="4.5.11" />
<PackageReference Include="NLog.Web.AspNetCore" Version="4.8.0" />
<PackageReference Include="protobuf-net" Version="3.0.0-alpha.3" />
<PackageReference Include="SteamKit2" Version="2.2.0-Beta.1" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.0.0-beta" />
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="5.0.0-beta" />
<PackageReference Include="System.Composition" Version="1.3.0-preview.18571.3" />
<PackageReference Include="System.Composition" Version="1.3.0-preview.19073.11" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp2.2'">
<PackageReference Include="ILLink.Tasks" Version="0.1.5-preview-1841731" />
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="4.6.0-preview.18571.3" />
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="4.6.0-preview.19073.11" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net472'">
<PackageReference Include="System.Collections.Immutable" Version="1.6.0-preview.18571.3" />
<PackageReference Include="System.Collections.Immutable" Version="1.6.0-preview.19073.11" />
<Reference Include="System.Net.Http">
<HintPath>C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.7.2\System.Net.Http.dll</HintPath>
</Reference>
@@ -133,10 +132,4 @@
</Content>
</ItemGroup>
<ItemGroup>
<Reference Include="SteamKit2">
<HintPath>lib\SteamKit2.dll</HintPath>
</Reference>
</ItemGroup>
</Project>

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,12 @@ namespace ArchiSteamFarm {
continue;
}
uint realAppID = 0;
Steam.Asset.EType type = Steam.Asset.EType.Unknown;
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, description.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 +200,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 +1133,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 +1184,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 +1209,39 @@ namespace ArchiSteamFarm {
bool marketable = description["marketable"].AsBoolean();
uint realAppID = 0;
Steam.Asset.EType type = Steam.Asset.EType.Unknown;
Steam.Asset.ERarity rarity = Steam.Asset.ERarity.Unknown;
uint realAppID = 0;
if (appID == Steam.Asset.SteamAppID) {
string hashName = description["market_hash_name"].Value;
List<KeyValue> tags = description["tags"].Children;
if (!string.IsNullOrEmpty(hashName)) {
realAppID = GetAppIDFromMarketHashName(hashName);
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));
}
string descriptionType = description["type"].Value;
if (!string.IsNullOrEmpty(descriptionType)) {
type = GetItemType(descriptionType);
}
(type, rarity, realAppID) = Steam.InventoryResponse.Description.InterpretTags(parsedTags);
}
descriptions[(appID, classID)] = (marketable, realAppID, type);
descriptions[(appID, classID)] = (marketable, realAppID, type, rarity);
}
HashSet<Steam.TradeOffer> result = new HashSet<Steam.TradeOffer>();
@@ -1612,7 +1615,7 @@ namespace ArchiSteamFarm {
return null;
}
result[appID] = game["name"].Value;
result[appID] = game["name"].AsString();
}
return result;
@@ -1911,7 +1914,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 +1922,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 +2244,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 +2316,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 +2359,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,7 +198,8 @@ namespace ArchiSteamFarm.Json {
TradingCard,
SteamGems,
SaleItem,
Consumable
Consumable,
ProfileModifier
}
}
@@ -391,10 +404,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 +449,12 @@ 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;
internal ulong ClassID { get; private set; }
internal bool Marketable { get; private set; }
internal Asset.ERarity Rarity { get; private set; }
internal uint RealAppID { 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 +480,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, RealAppID) = InterpretTags(value);
}
}
[JsonProperty(PropertyName = "tradable", Required = Required.Always)]
private byte TradableNumber {
set => Tradable = value > 0;
@@ -477,13 +500,157 @@ namespace ArchiSteamFarm.Json {
[JsonConstructor]
private Description() { }
internal static (Asset.EType Type, Asset.ERarity Rarity, uint RealAppID) InterpretTags(IReadOnlyCollection<Tag> tags) {
if ((tags == null) || (tags.Count == 0)) {
ASF.ArchiLogger.LogNullError(nameof(tags));
return (Asset.EType.Unknown, Asset.ERarity.Unknown, 0);
}
Asset.EType type = Asset.EType.Unknown;
Asset.ERarity rarity = Asset.ERarity.Unknown;
uint realAppID = 0;
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 "Game":
if ((tag.Value.Length <= 4) || !tag.Value.StartsWith("app_", StringComparison.Ordinal)) {
ASF.ArchiLogger.LogGenericError(string.Format(Strings.WarningUnknownValuePleaseReport, nameof(tag.Value), tag.Value));
break;
}
string appIDText = tag.Value.Substring(4);
if (!uint.TryParse(appIDText, out uint appID) || (appID == 0)) {
ASF.ArchiLogger.LogNullError(nameof(appID));
break;
}
realAppID = appID;
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, realAppID);
}
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

@@ -646,7 +646,9 @@ StackTrace:
<value>Řešení pro chybu {0} bylo spuštěno.</value>
<comment>{0} will be replaced by the bug's name provided by ASF</comment>
</data>
<data name="TargetBotNotConnected" xml:space="preserve">
<value>Tato instance bota není připojena!</value>
</data>
<data name="BotWalletBalance" xml:space="preserve">
<value>Zůstatek v peněžence: {0} {1}</value>
<comment>{0} will be replaced by wallet balance value, {1} will be replaced by currency name</comment>
@@ -658,14 +660,29 @@ StackTrace:
<value>Bot má level {0}.</value>
<comment>{0} will be replaced by bot's level</comment>
</data>
<data name="ActivelyMatchingItems" xml:space="preserve">
<value>Srovnávám položky ze Steamu, kolo #{0}...</value>
<comment>{0} will be replaced by round number</comment>
</data>
<data name="DoneActivelyMatchingItems" xml:space="preserve">
<value>Položky ze Steamu porovnány, kolo #{0}.</value>
<comment>{0} will be replaced by round number</comment>
</data>
<data name="ErrorAborted" xml:space="preserve">
<value>Přerušeno!</value>
</data>
<data name="ActivelyMatchingItemsRound" xml:space="preserve">
<value>Celkem porovnáno {0} sad karet.</value>
<comment>{0} will be replaced by number of sets traded</comment>
</data>
<data name="WarningExcessiveBotsCount" xml:space="preserve">
<value>Používáte více osobních účtů pro boty, než je náš doporučený limit ({0}). Upozorňujeme, že takové nastavení není podporováno a může způsobovat různé problémy se službou Steam, včetně pozastavení nebo blokací účtů. Pro další informace se podívejte na FAQ.</value>
<comment>{0} will be replaced by our maximum recommended bots count (number)</comment>
</data>
<data name="PluginLoaded" xml:space="preserve">
<value>Plugin {0} byl úspěšně načten!</value>
<comment>{0} will be replaced by the name of the custom ASF plugin</comment>
</data>
<data name="PluginLoading" xml:space="preserve">
<value>Načítám {0} V{1}...</value>
<comment>{0} will be replaced by the name of the custom ASF plugin, {1} will be replaced by its version</comment>
@@ -673,5 +690,7 @@ StackTrace:
<data name="NothingFound" xml:space="preserve">
<value>Nic nenalezeno!</value>
</data>
<data name="PluginsWarning" xml:space="preserve">
<value>Jeden nebo více vlastních pluginů bylo načteno do ASF. Jelikož nejsme schopni poskytnout podporu pro modifikované nastavení, prosím, v případě problémů kontaktujte příslušné vývojáře daných pluginů.</value>
</data>
</root>

View File

@@ -613,7 +613,7 @@
<value>Доступ заборонено!</value>
</data>
<data name="WarningPreReleaseVersion" xml:space="preserve">
<value>Ви користуєтеся версією, яка новіша за останню версію у цьому каналі оновлення. Будь ласка, зверніть увагу, що пре-релізні версіі призначені для користувачів які вміють доповідати про помилки, вирішувати питання та надавати зворотній зв'язок - технічна підтримка не надається.</value>
<value>Ви користуєтеся версією, яка новіша за останню версію у цьому каналі оновлення. Будь ласка, зверніть увагу, що підготовчі версії призначені для користувачів які вміють доповідати про помилки, вирішувати питання та надавати зворотній зв'язок - технічна підтримка не надається.</value>
</data>
<data name="BotStats" xml:space="preserve">
<value>Поточне використання пам'яті: {0} МБ.</value>

View File

@@ -0,0 +1,696 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string"/>
<xsd:attribute name="type" type="xsd:string"/>
<xsd:attribute name="mimetype" type="xsd:string"/>
<xsd:attribute ref="xml:space"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string"/>
<xsd:attribute name="name" type="xsd:string"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
<xsd:attribute ref="xml:space"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required"/>
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="AcceptingTrade" xml:space="preserve">
<value>接受交易︰{0}</value>
<comment>{0} will be replaced by trade number</comment>
</data>
<data name="AutoUpdateCheckInfo" xml:space="preserve">
<value>ASF 將每 {0} 自動檢查新版本。</value>
<comment>{0} will be replaced by translated TimeSpan string (such as "24 hours")</comment>
</data>
<data name="Content" xml:space="preserve">
<value>內容:
{0}</value>
<comment>{0} will be replaced by content string. Please note that this string should include newline for formatting.</comment>
</data>
<data name="ErrorConfigPropertyInvalid" xml:space="preserve">
<value>配置 {0} 屬性無效: {1}</value>
<comment>{0} will be replaced by name of the configuration property, {1} will be replaced by invalid value</comment>
</data>
<data name="ErrorEarlyFatalExceptionInfo" xml:space="preserve">
<value>ASF v{0} 在能初始化核心日誌記錄模組之前就遇到了嚴重異常!</value>
<comment>{0} will be replaced by version number</comment>
</data>
<data name="ErrorEarlyFatalExceptionPrint" xml:space="preserve">
<value>例外錯誤:{0}() {1}
堆疊追蹤:
{2}</value>
<comment>{0} will be replaced by function name, {1} will be replaced by exception message, {2} will be replaced by entire stack trace. Please note that this string should include newlines for formatting.</comment>
</data>
<data name="ErrorExitingWithNonZeroErrorCode" xml:space="preserve">
<value>使用非零錯誤代碼退出!</value>
</data>
<data name="ErrorFailingRequest" xml:space="preserve">
<value>請求失敗: {0}</value>
<comment>{0} will be replaced by URL of the request</comment>
</data>
<data name="ErrorGlobalConfigNotLoaded" xml:space="preserve">
<value>無法載入全域配置。請確保 {0} 存在且有效! 如果仍有疑惑, 請參閱wiki上的 "使用者入門" 指南。</value>
<comment>{0} will be replaced by file's path</comment>
</data>
<data name="ErrorIsInvalid" xml:space="preserve">
<value>{0} 無效!</value>
<comment>{0} will be replaced by object's name</comment>
</data>
<data name="ErrorMobileAuthenticatorInvalidDeviceID" xml:space="preserve">
<value>ASF 雙重認證中設定的 DeviceID 無效,該功能無法執行!</value>
</data>
<data name="ErrorNoBotsDefined" xml:space="preserve">
<value>沒有設定任何機器人。您是否忘記配置 ASF </value>
</data>
<data name="ErrorObjectIsNull" xml:space="preserve">
<value>{0} 為空!</value>
<comment>{0} will be replaced by object's name</comment>
</data>
<data name="ErrorParsingObject" xml:space="preserve">
<value>解析 {0} 失敗!</value>
<comment>{0} will be replaced by object's name</comment>
</data>
<data name="ErrorRequestFailedTooManyTimes" xml:space="preserve">
<value>嘗試請求 {0} 次後失敗!</value>
<comment>{0} will be replaced by maximum number of tries</comment>
</data>
<data name="ErrorUpdateCheckFailed" xml:space="preserve">
<value>無法檢查最新版本!</value>
</data>
<data name="ErrorUpdateNoAssetForThisVersion" xml:space="preserve">
<value>無法繼續更新, 因為沒有與當前正在運行的版本相關的資產! 無法自動更新該版本。</value>
</data>
<data name="ErrorUpdateNoAssets" xml:space="preserve">
<value>無法繼續進行更新, 因為該版本未提供任何資產!</value>
</data>
<data name="ErrorUserInputRunningInHeadlessMode" xml:space="preserve">
<value>收到一個使用者輸入請求, 但進程當前正在無頭模式下運行!</value>
</data>
<data name="Exiting" xml:space="preserve">
<value>正在退出...</value>
</data>
<data name="WarningFailed" xml:space="preserve">
<value>失敗!</value>
</data>
<data name="GlobalConfigChanged" xml:space="preserve">
<value>全域設定檔已更改!</value>
</data>
<data name="ErrorGlobalConfigRemoved" xml:space="preserve">
<value>全域設定檔已被刪除!</value>
</data>
<data name="IgnoringTrade" xml:space="preserve">
<value>忽略交易: {0}</value>
<comment>{0} will be replaced by trade number</comment>
</data>
<data name="LoggingIn" xml:space="preserve">
<value>正在登錄到 {0}...</value>
<comment>{0} will be replaced by service's name</comment>
</data>
<data name="NoBotsAreRunning" xml:space="preserve">
<value>沒有運行中的機器人,正在退出...</value>
</data>
<data name="RefreshingOurSession" xml:space="preserve">
<value>刷新工作階段 </value>
</data>
<data name="RejectingTrade" xml:space="preserve">
<value>拒絕交易︰ {0}</value>
<comment>{0} will be replaced by trade number</comment>
</data>
<data name="Restarting" xml:space="preserve">
<value>重新啟動中...</value>
</data>
<data name="Starting" xml:space="preserve">
<value>正在啟動...</value>
</data>
<data name="Success" xml:space="preserve">
<value>成功!</value>
</data>
<data name="UnlockingParentalAccount" xml:space="preserve">
<value>正在解鎖家庭監護帳戶...</value>
</data>
<data name="UpdateCheckingNewVersion" xml:space="preserve">
<value>正在檢查新版本...</value>
</data>
<data name="UpdateDownloadingNewVersion" xml:space="preserve">
<value>正在下載新版本:{0} ({1} MB)... 等待期間,如果喜歡這個軟體,請考慮捐助 ASF :)</value>
<comment>{0} will be replaced by version string, {1} will be replaced by update size (in megabytes)</comment>
</data>
<data name="UpdateFinished" xml:space="preserve">
<value>更新完成 </value>
</data>
<data name="UpdateNewVersionAvailable" xml:space="preserve">
<value>ASF 有新版本可用!請考慮手動更新!</value>
</data>
<data name="UpdateVersionInfo" xml:space="preserve">
<value>本地版本: {0} | 遠端版本: {1}</value>
<comment>{0} will be replaced by current version, {1} will be replaced by remote version</comment>
</data>
<data name="UserInputDeviceID" xml:space="preserve">
<value>請輸入您的移動身份驗證器設備 ID (包括 "android:") </value>
<comment>Please note that this translation should end with space</comment>
</data>
<data name="UserInputSteam2FA" xml:space="preserve">
<value>請輸入您的 Steam 身份驗證器應用程式上的兩步驟驗證代碼: </value>
<comment>Please note that this translation should end with space</comment>
</data>
<data name="UserInputSteamGuard" xml:space="preserve">
<value>請輸入寄送至您的電子信箱的 Steam Guard 驗證代碼: </value>
<comment>Please note that this translation should end with space</comment>
</data>
<data name="UserInputSteamLogin" xml:space="preserve">
<value>請輸入您的 Steam 帳號: </value>
<comment>Please note that this translation should end with space</comment>
</data>
<data name="UserInputSteamParentalCode" xml:space="preserve">
<value>請輸入 Steam 家庭監護 PIN 碼: </value>
<comment>Please note that this translation should end with space</comment>
</data>
<data name="UserInputSteamPassword" xml:space="preserve">
<value>請輸入您的 Steam 密碼: </value>
<comment>Please note that this translation should end with space</comment>
</data>
<data name="UserInputUnknown" xml:space="preserve">
<value>請輸入未記錄的值 {0} </value>
<comment>{0} will be replaced by property name. Please note that this translation should end with space</comment>
</data>
<data name="WarningUnknownValuePleaseReport" xml:space="preserve">
<value>收到 {0} 的未知值,請回報這個問題:{1}</value>
<comment>{0} will be replaced by object's name, {1} will be replaced by value for that object</comment>
</data>
<data name="IPCReady" xml:space="preserve">
<value>IPC 伺服器準備就緒!</value>
</data>
<data name="IPCStarting" xml:space="preserve">
<value>IPC 伺服器啟動中...</value>
</data>
<data name="BotAlreadyStopped" xml:space="preserve">
<value>這個機器人已經停止了!</value>
</data>
<data name="BotNotFound" xml:space="preserve">
<value>找不到任何名為 {0} 的機器人!</value>
<comment>{0} will be replaced by bot's name query (string)</comment>
</data>
<data name="BotStatusOverview" xml:space="preserve">
<value>{0}/{1} 個機器人正在運行,總共 {2} 個遊戲 ({3} 張卡片) 等待掛卡。</value>
<comment>{0} will be replaced by number of active bots, {1} will be replaced by total number of bots, {2} will be replaced by total number of games left to idle, {3} will be replaced by total number of cards left to idle</comment>
</data>
<data name="BotStatusIdling" xml:space="preserve">
<value>機器人正在掛卡的遊戲:{0} ({1},剩餘 {2} 張卡片可以掉落),總共剩餘 {3} 個遊戲 ( {4} 張卡片) 等待掛卡 (~需時約 {5})。</value>
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name, {2} will be replaced by number of cards left to idle, {3} will be replaced by total number of games to idle, {4} will be replaced by total number of cards to idle, {5} will be replaced by translated TimeSpan string (such as "1 day, 5 hours and 30 minutes")</comment>
</data>
<data name="BotStatusIdlingList" xml:space="preserve">
<value>機器人正在掛卡的遊戲:{0},總共剩餘 {1} 個遊戲 ({2} 張卡片) 等待掛卡 (~需時約 {3})。</value>
<comment>{0} will be replaced by list of the games (IDs, numbers), {1} will be replaced by total number of games to idle, {2} will be replaced by total number of cards to idle, {3} will be replaced by translated TimeSpan string (such as "1 day, 5 hours and 30 minutes")</comment>
</data>
<data name="CheckingFirstBadgePage" xml:space="preserve">
<value>正在檢查徽章頁首頁...</value>
</data>
<data name="CheckingOtherBadgePages" xml:space="preserve">
<value>正在檢查其他徽章頁...</value>
</data>
<data name="ChosenFarmingAlgorithm" xml:space="preserve">
<value>選擇的掛卡演算法為:{0}</value>
<comment>{0} will be replaced by the name of chosen idling algorithm</comment>
</data>
<data name="Done" xml:space="preserve">
<value>完成 </value>
</data>
<data name="GamesToIdle" xml:space="preserve">
<value>總共有 {0} 個遊戲 ({1} 張卡片) 等待掛卡 (~需時約 {2})...</value>
<comment>{0} will be replaced by number of games, {1} will be replaced by number of cards, {2} will be replaced by translated TimeSpan string (such as "1 day, 5 hours and 30 minutes")</comment>
</data>
<data name="IdlingFinished" xml:space="preserve">
<value>掛卡完成!</value>
</data>
<data name="IdlingFinishedForGame" xml:space="preserve">
<value>完成掛卡:{0} ({1}),耗時 {2}</value>
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name, {2} will be replaced by translated TimeSpan string (such as "1 day, 5 hours and 30 minutes")</comment>
</data>
<data name="IdlingFinishedForGames" xml:space="preserve">
<value>已完成掛卡的遊戲:{0}</value>
<comment>{0} will be replaced by list of the games (IDs, numbers), separated by a comma</comment>
</data>
<data name="IdlingStatusForGame" xml:space="preserve">
<value>{0} ({1}) 的掛卡狀態:剩餘 {2} 張卡片</value>
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name, {2} will be replaced by number of cards left to idle</comment>
</data>
<data name="IdlingStopped" xml:space="preserve">
<value>掛卡停止!</value>
</data>
<data name="IgnoredPermanentPauseEnabled" xml:space="preserve">
<value>忽略此請求,因為強制暫停已啟用!</value>
</data>
<data name="NothingToIdle" xml:space="preserve">
<value>這個帳戶目前沒有需要掛卡的遊戲!</value>
</data>
<data name="NowIdling" xml:space="preserve">
<value>正在掛卡:{0} ({1})</value>
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name</comment>
</data>
<data name="NowIdlingList" xml:space="preserve">
<value>正在掛卡︰ {0}</value>
<comment>{0} will be replaced by list of the games (IDs, numbers), separated by a comma</comment>
</data>
<data name="PlayingNotAvailable" xml:space="preserve">
<value>當前無法執行,我們將稍後再試!</value>
</data>
<data name="StillIdling" xml:space="preserve">
<value>仍在掛卡:{0} ({1})</value>
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name</comment>
</data>
<data name="StillIdlingList" xml:space="preserve">
<value>仍在掛卡:{0}</value>
<comment>{0} will be replaced by list of the games (IDs, numbers), separated by a comma</comment>
</data>
<data name="StoppedIdling" xml:space="preserve">
<value>停止掛卡:{0} ({1})</value>
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name</comment>
</data>
<data name="StoppedIdlingList" xml:space="preserve">
<value>停止掛卡:{0}</value>
<comment>{0} will be replaced by list of the games (IDs, numbers), separated by a comma</comment>
</data>
<data name="UnknownCommand" xml:space="preserve">
<value>未知指令!</value>
</data>
<data name="WarningCouldNotCheckBadges" xml:space="preserve">
<value>無法取得徽章頁資訊,我們將稍後再試!</value>
</data>
<data name="WarningCouldNotCheckCardsStatus" xml:space="preserve">
<value>無法檢查卡片狀態:{0} ({1}),我們將稍後再試!</value>
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name</comment>
</data>
<data name="BotAcceptingGift" xml:space="preserve">
<value>接受禮物:{0}...</value>
<comment>{0} will be replaced by giftID (number)</comment>
</data>
<data name="BotAccountLimited" xml:space="preserve">
<value>這個帳戶是受限制的帳戶,在限制解除前將無法掛卡!</value>
</data>
<data name="BotAddLicense" xml:space="preserve">
<value>ID{0} | 狀態:{1}</value>
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by status string</comment>
</data>
<data name="BotAddLicenseWithItems" xml:space="preserve">
<value>ID{0} | 狀態:{1} | 物品:{2}</value>
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by status string, {2} will be replaced by list of granted IDs (numbers), separated by a comma</comment>
</data>
<data name="BotAlreadyRunning" xml:space="preserve">
<value>該機器人已在運行了!</value>
</data>
<data name="BotAuthenticatorConverting" xml:space="preserve">
<value>正在將. maFile 轉換為 ASF 格式..</value>
</data>
<data name="BotAuthenticatorImportFinished" xml:space="preserve">
<value>已成功匯入行動驗證器 </value>
</data>
<data name="BotAuthenticatorInvalidDeviceID" xml:space="preserve">
<value>您的 DeviceID 不正確或不存在!</value>
</data>
<data name="BotAuthenticatorToken" xml:space="preserve">
<value>兩步驟驗證代碼:{0}</value>
<comment>{0} will be replaced by generated 2FA token (string)</comment>
</data>
<data name="BotAutomaticIdlingNowPaused" xml:space="preserve">
<value>自動掛卡已暫停!</value>
</data>
<data name="BotAutomaticIdlingNowResumed" xml:space="preserve">
<value>自動掛卡已恢復!</value>
</data>
<data name="BotAutomaticIdlingPausedAlready" xml:space="preserve">
<value>自動掛卡已暫停!</value>
</data>
<data name="BotAutomaticIdlingResumedAlready" xml:space="preserve">
<value>自動掛卡已恢復!</value>
</data>
<data name="BotConnected" xml:space="preserve">
<value>已連線到 Steam</value>
</data>
<data name="BotDisconnected" xml:space="preserve">
<value>已與 Steam 中斷連線!</value>
</data>
<data name="BotDisconnecting" xml:space="preserve">
<value>正在中斷連線...</value>
</data>
<data name="BotEncryptedPassword" xml:space="preserve">
<value>[{0}] 密碼︰{1}</value>
<comment>{0} will be replaced by password encryption method (string), {1} will be replaced by encrypted password using that method (string)</comment>
</data>
<data name="BotInstanceNotStartingBecauseDisabled" xml:space="preserve">
<value>不會啟動此機器人實例, 因為它在設定檔中被禁用!</value>
</data>
<data name="BotInvalidAuthenticatorDuringLogin" xml:space="preserve">
<value>已連續收到 {0} 次 TwoFactorCodeMismatch 錯誤訊息。您的兩步驟驗證憑證可能已失效,或時鐘不同步,正在中止!</value>
<comment>{0} will be replaced by maximum allowed number of failed 2FA attempts</comment>
</data>
<data name="BotLoggedOff" xml:space="preserve">
<value>已從 Steam 登出:{0}</value>
<comment>{0} will be replaced by logging off reason (string)</comment>
</data>
<data name="BotLoggedOn" xml:space="preserve">
<value>{0} 已成功登入。</value>
<comment>{0} will be replaced by steam ID (number)</comment>
</data>
<data name="BotLoggingIn" xml:space="preserve">
<value>登入中...</value>
</data>
<data name="BotLogonSessionReplaced" xml:space="preserve">
<value>這個帳戶似乎正被另一個 ASF 使用中,此為未定義行為,拒絕讓它繼續執行!</value>
</data>
<data name="BotLootingFailed" xml:space="preserve">
<value>交易提案已失敗!</value>
</data>
<data name="BotLootingMasterNotDefined" xml:space="preserve">
<value>無法發送交易提案,因為沒有帳戶設有 master 權限!</value>
</data>
<data name="BotLootingSuccess" xml:space="preserve">
<value>交易提案發送成功!</value>
</data>
<data name="BotSendingTradeToYourself" xml:space="preserve">
<value>您無法發送交易給自己!</value>
</data>
<data name="BotNoASFAuthenticator" xml:space="preserve">
<value>該機器人並未啟用 ASF 二階段驗證!您是否忘記將驗證器導入成 ASF 二階段驗證?</value>
</data>
<data name="BotNotConnected" xml:space="preserve">
<value>此機器人實例未連接!</value>
</data>
<data name="BotNotOwnedYet" xml:space="preserve">
<value>尚未擁有: {0}</value>
<comment>{0} will be replaced by query (string)</comment>
</data>
<data name="BotOwnedAlreadyWithName" xml:space="preserve">
<value>已擁有:{0} | {1}</value>
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name</comment>
</data>
<data name="BotRateLimitExceeded" xml:space="preserve">
<value>超過頻率限制,我們將在 {0} 的冷卻時間後重試...</value>
<comment>{0} will be replaced by translated TimeSpan string (such as "25 minutes")</comment>
</data>
<data name="BotReconnecting" xml:space="preserve">
<value>正在重新連線...</value>
</data>
<data name="BotRedeem" xml:space="preserve">
<value>產品序號:{0} | 狀態:{1}</value>
<comment>{0} will be replaced by cd-key (string), {1} will be replaced by status string</comment>
</data>
<data name="BotRedeemWithItems" xml:space="preserve">
<value>產品序號:{0} | 狀態:{1} | 物品:{2}</value>
<comment>{0} will be replaced by cd-key (string), {1} will be replaced by status string, {2} will be replaced by list of key-value pairs, separated by a comma</comment>
</data>
<data name="BotRemovedExpiredLoginKey" xml:space="preserve">
<value>已刪除過期的登錄金鑰!</value>
</data>
<data name="BotStatusNotIdling" xml:space="preserve">
<value>機器人當前沒有在掛卡。</value>
</data>
<data name="BotStatusLimited" xml:space="preserve">
<value>當前機器人為受限帳戶,所以無法通過掛卡得到卡片。</value>
</data>
<data name="BotStatusConnecting" xml:space="preserve">
<value>機器人正在連接到Steam網路。</value>
</data>
<data name="BotStatusNotRunning" xml:space="preserve">
<value>機器人未運行。</value>
</data>
<data name="BotStatusPaused" xml:space="preserve">
<value>機器人已暫停或正在手動模式下運行。</value>
</data>
<data name="BotStatusPlayingNotAvailable" xml:space="preserve">
<value>機器人當前正在使用中。</value>
</data>
<data name="BotUnableToLogin" xml:space="preserve">
<value>無法登錄到 steam: {0}/{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
</data>
<data name="ErrorIsEmpty" xml:space="preserve">
<value>{0} 為空!</value>
<comment>{0} will be replaced by object's name</comment>
</data>
<data name="UnusedKeys" xml:space="preserve">
<value>未使用的產品序號:{0}</value>
<comment>{0} will be replaced by list of cd-keys (strings), separated by a comma</comment>
</data>
<data name="WarningFailedWithError" xml:space="preserve">
<value>由於錯誤{0} 而失敗:</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotConnectionLost" xml:space="preserve">
<value>與 Steam 網路的連線中斷,正在重新進行連線...</value>
</data>
<data name="BotAccountFree" xml:space="preserve">
<value>帳戶不再被佔用,已恢復掛卡!</value>
</data>
<data name="BotAccountOccupied" xml:space="preserve">
<value>帳戶目前正被使用: ASF將在該帳戶空閒時繼續掛卡...</value>
</data>
<data name="BotConnecting" xml:space="preserve">
<value>連接...</value>
</data>
<data name="BotHeartBeatFailed" xml:space="preserve">
<value>無法斷開與用戶端的連接。正在中止這個機器人實例!</value>
</data>
<data name="BotSteamDirectoryInitializationFailed" xml:space="preserve">
<value>無法初始化Steam目錄: 與Steam網路的連接可能需要比平時更長的時間!</value>
</data>
<data name="BotStopping" xml:space="preserve">
<value>停止中...</value>
</data>
<data name="ErrorBotConfigInvalid" xml:space="preserve">
<value>您的機器人配置無效。請驗證 {0} 的內容, 然後重試!</value>
<comment>{0} will be replaced by file's path</comment>
</data>
<data name="ErrorDatabaseInvalid" xml:space="preserve">
<value>無法載入持久資料庫, 如果問題仍然存在, 請刪除 {0}, 以重新創建資料庫!</value>
<comment>{0} will be replaced by file's path</comment>
</data>
<data name="Initializing" xml:space="preserve">
<value>正在初始化{0}…</value>
<comment>{0} will be replaced by service name that is being initialized</comment>
</data>
<data name="WarningPrivacyPolicy" xml:space="preserve">
<value>如果您對 ASF 的實際運作方式有疑慮請查看我們wiki 中關於隱私政策的部分!</value>
</data>
<data name="Welcome" xml:space="preserve">
<value>看來這是您首次使用 ASF歡迎</value>
</data>
<data name="ErrorInvalidCurrentCulture" xml:space="preserve">
<value>您提供的 CurrentCulture 無效ASF 將以預設值繼續運行 </value>
</data>
<data name="TranslationIncomplete" xml:space="preserve">
<value>ASF 將嘗試使用你的偏好語系 {0},但該語言的翻譯僅完成了 {1}。也許您可以協助我們改善 ASF 於您的語言的翻譯品質。</value>
<comment>{0} will be replaced by culture code, such as "en-US", {1} will be replaced by completeness percentage, such as "78.5%"</comment>
</data>
<data name="IdlingGameNotPossible" xml:space="preserve">
<value>{0} ({1}) 的掛卡被暫時禁用,因為 ASF 目前無法執行該遊戲。</value>
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name</comment>
</data>
<data name="WarningIdlingGameMismatch" xml:space="preserve">
<value>ASF 檢測到 {0} ({1}) 的 ID 不匹配,並將改為使用 ID {2}。</value>
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name, {2} will be replaced by game's ID (number)</comment>
</data>
<data name="BotVersion" xml:space="preserve">
<value>{0} V{1}</value>
<comment>{0} will be replaced by program's name (e.g. "ASF"), {1} will be replaced by program's version (e.g. "1.0.0.0"). This string typically has nothing to translate and you should leave it as it is, unless you need to change the format, e.g. in RTL languages.</comment>
</data>
<data name="BotAccountLocked" xml:space="preserve">
<value>此帳戶已被鎖定,永久無法掛卡 </value>
</data>
<data name="BotStatusLocked" xml:space="preserve">
<value>機器人已被鎖定,無法透過掛卡得到卡片。</value>
</data>
<data name="ErrorFunctionOnlyInHeadlessMode" xml:space="preserve">
<value>此功能僅在無頭模式下可用!</value>
</data>
<data name="BotOwnedAlready" xml:space="preserve">
<value>已擁有:{0}</value>
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name</comment>
</data>
<data name="ErrorAccessDenied" xml:space="preserve">
<value>訪問被拒!</value>
</data>
<data name="WarningPreReleaseVersion" xml:space="preserve">
<value>您目前使用的版本高於最新的穩定版本。請注意:預發行版本專用於了解如何回報錯誤、處理問題並提供回饋的使用者,並不提供任何技術支援。</value>
</data>
<data name="BotStats" xml:space="preserve">
<value>當前記憶體使用量:{0} MB。</value>
<comment>{0} will be replaced by number (in megabytes) of memory being used</comment>
</data>
<data name="ClearingDiscoveryQueue" xml:space="preserve">
<value>正在瀏覽 Steam 探索佇列 #{0}...</value>
<comment>{0} will be replaced by queue number</comment>
</data>
<data name="DoneClearingDiscoveryQueue" xml:space="preserve">
<value>已完成 Steam 探索佇列 #{0}。</value>
<comment>{0} will be replaced by queue number</comment>
</data>
<data name="BotOwnsOverviewPerGame" xml:space="preserve">
<value>{0}/{1} 個機器人已經擁有遊戲 {2}。</value>
<comment>{0} will be replaced by number of bots that already own particular game being checked, {1} will be replaced by total number of bots that were checked during the process, {2} will be replaced by game's ID (number)</comment>
</data>
<data name="BotRefreshingPackagesData" xml:space="preserve">
<value>更新包資料中...</value>
</data>
<data name="WarningDeprecated" xml:space="preserve">
<value>{0} 的用法已棄用, 並將在程式的未來版本中刪除。請使用 {1}。</value>
<comment>{0} will be replaced by the name of deprecated property (such as argument, config property or likewise), {1} will be replaced by the name of valid replacement (such as another argument or config property)</comment>
</data>
<data name="BotAcceptedDonationTrade" xml:space="preserve">
<value>接受捐贈交易:{0}</value>
<comment>{0} will be replaced by trade's ID (number)</comment>
</data>
<data name="WarningWorkaroundTriggered" xml:space="preserve">
<value>已觸發錯誤 {0} 的解決方法。</value>
<comment>{0} will be replaced by the bug's name provided by ASF</comment>
</data>
<data name="TargetBotNotConnected" xml:space="preserve">
<value>目標機器人實例未連線!</value>
</data>
<data name="BotWalletBalance" xml:space="preserve">
<value>錢包餘額:{0} {1}</value>
<comment>{0} will be replaced by wallet balance value, {1} will be replaced by currency name</comment>
</data>
<data name="BotHasNoWallet" xml:space="preserve">
<value>當前機器人無錢包餘額。</value>
</data>
<data name="BotLevel" xml:space="preserve">
<value>當前機器人的等級為 {0}。</value>
<comment>{0} will be replaced by bot's level</comment>
</data>
<data name="ActivelyMatchingItems" xml:space="preserve">
<value>正在匹配 Steam 物品,第 #{0} 輪...</value>
<comment>{0} will be replaced by round number</comment>
</data>
<data name="DoneActivelyMatchingItems" xml:space="preserve">
<value>已完成匹配 Steam 物品,第 #{0} 輪。</value>
<comment>{0} will be replaced by round number</comment>
</data>
<data name="ErrorAborted" xml:space="preserve">
<value>已中止!</value>
</data>
<data name="ActivelyMatchingItemsRound" xml:space="preserve">
<value>此輪共匹配 {0} 套物品。</value>
<comment>{0} will be replaced by number of sets traded</comment>
</data>
<data name="WarningExcessiveBotsCount" xml:space="preserve">
<value>您運行的個人機器人帳戶數目已經超出我們建議的上限 ({0}) 。請注意, 此設置不受支援, 可能會導致各種Stean相關的題, 包括帳戶停權 。有關詳細資訊, 請參閱常見問題解答。</value>
<comment>{0} will be replaced by our maximum recommended bots count (number)</comment>
</data>
<data name="PluginLoaded" xml:space="preserve">
<value>{0} 載入成功!</value>
<comment>{0} will be replaced by the name of the custom ASF plugin</comment>
</data>
<data name="PluginLoading" xml:space="preserve">
<value>正在載入 {0} V{1}...</value>
<comment>{0} will be replaced by the name of the custom ASF plugin, {1} will be replaced by its version</comment>
</data>
<data name="NothingFound" xml:space="preserve">
<value>未找到任何內容!</value>
</data>
<data name="PluginsWarning" xml:space="preserve">
<value>您已將一個或多個自訂外掛程式載入到ASF中。由於我們無法支援修改過的程序 如遭遇任何問題,請向相關外掛程式的開發者尋求協助。</value>
</data>
</root>

View File

@@ -186,7 +186,7 @@
<value>無法進行更新,因為此版本沒有提供任何資源文件!</value>
</data>
<data name="ErrorUserInputRunningInHeadlessMode" xml:space="preserve">
<value>收到一個使用者輸入請求,但程序目前正以無介面模式執行!</value>
<value>收到一個使用者輸入請求,但程序目前正以無模式執行!</value>
</data>
<data name="Exiting" xml:space="preserve">
<value>正在退出...</value>
@@ -534,7 +534,7 @@
<comment>{0} will be replaced by list of cd-keys (strings), separated by a comma</comment>
</data>
<data name="WarningFailedWithError" xml:space="preserve">
<value>因發生錯誤而失敗{0}</value>
<value>因發生錯誤{0} 而失敗</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotConnectionLost" xml:space="preserve">
@@ -602,7 +602,7 @@
<value>BOT 已被鎖定,無法透過掛卡得到卡片。</value>
</data>
<data name="ErrorFunctionOnlyInHeadlessMode" xml:space="preserve">
<value>此功能僅能在無介面模式下使用!</value>
<value>此功能僅能在無模式下使用!</value>
</data>
<data name="BotOwnedAlready" xml:space="preserve">
<value>已擁有:{0}</value>

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

Binary file not shown.

File diff suppressed because it is too large Load Diff

2
wiki

Submodule wiki updated: 92db2be904...b2825eccc5