mirror of
https://github.com/JustArchiNET/ArchiSteamFarm.git
synced 2025-12-16 14:30:31 +00:00
Compare commits
56 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a062e98f2d | ||
|
|
8b8671c679 | ||
|
|
5e4f6a7926 | ||
|
|
57b2c8f1cf | ||
|
|
02e7f2144f | ||
|
|
c13fd10bd8 | ||
|
|
7fe0b4499b | ||
|
|
94160bdc42 | ||
|
|
c01c0f7cd1 | ||
|
|
65b06ae3b9 | ||
|
|
5d443f3ed2 | ||
|
|
6a6c903d7d | ||
|
|
52f5ef2a39 | ||
|
|
b92a4ea505 | ||
|
|
8ae9db3a34 | ||
|
|
388c72052c | ||
|
|
e965899395 | ||
|
|
5605e04f0a | ||
|
|
953103719c | ||
|
|
ea53fd0a87 | ||
|
|
f6770ea1c9 | ||
|
|
561b2a3566 | ||
|
|
d24e6d0302 | ||
|
|
98491a4562 | ||
|
|
08d7b9deb0 | ||
|
|
b904b1062f | ||
|
|
ad187c0d88 | ||
|
|
6a035f4832 | ||
|
|
021e8d2ad9 | ||
|
|
61609574d0 | ||
|
|
7b7f1518a6 | ||
|
|
6aa5a633e4 | ||
|
|
6f80ee9faa | ||
|
|
99e8df318c | ||
|
|
2ea334c62e | ||
|
|
27d0b7427a | ||
|
|
62c20c331e | ||
|
|
51d59f0f66 | ||
|
|
ab90c9dc68 | ||
|
|
f7c8b871b3 | ||
|
|
678b32f318 | ||
|
|
62221fd6b8 | ||
|
|
f932be1395 | ||
|
|
d8fd1035c3 | ||
|
|
09e8a52811 | ||
|
|
0b1032199b | ||
|
|
3e5dfb3174 | ||
|
|
4ada8595bd | ||
|
|
bc64c43748 | ||
|
|
f4f7935d4c | ||
|
|
aacc0a1720 | ||
|
|
e5ee909b96 | ||
|
|
f3f444d0bd | ||
|
|
43d18b6d49 | ||
|
|
f24be67c8c | ||
|
|
6ce4f2941b |
@@ -24,9 +24,9 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.5.0-preview-20170727-01" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="1.2.0-beta" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="1.2.0-beta" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.5.0-preview-20170810-02" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="1.2.0-beta3" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="1.2.0-beta3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -210,6 +210,13 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsUnixVersion(version)) {
|
||||
string executable = Path.Combine(targetDirectory, SharedInfo.AssemblyName);
|
||||
if (File.Exists(executable)) {
|
||||
OS.UnixSetFileAccessExecutable(executable);
|
||||
}
|
||||
}
|
||||
|
||||
ArchiLogger.LogGenericInfo(Strings.UpdateFinished);
|
||||
await RestartOrExit().ConfigureAwait(false);
|
||||
}
|
||||
@@ -268,6 +275,22 @@ namespace ArchiSteamFarm {
|
||||
Bot.RegisterBot(botName);
|
||||
}
|
||||
|
||||
private static bool IsUnixVersion(string version) {
|
||||
if (string.IsNullOrEmpty(version)) {
|
||||
ArchiLogger.LogNullError(nameof(version));
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (version) {
|
||||
case "linux-arm":
|
||||
case "linux-x64":
|
||||
case "osx-x64":
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsValidBotName(string botName) {
|
||||
if (string.IsNullOrEmpty(botName)) {
|
||||
ArchiLogger.LogNullError(nameof(botName));
|
||||
@@ -459,7 +482,14 @@ namespace ArchiSteamFarm {
|
||||
// Move top-level runtime in-use files to other directory
|
||||
// We must do it in order to not crash at later stage - all libraries/executables must keep original names
|
||||
foreach (string file in Directory.EnumerateFiles(targetDirectory)) {
|
||||
string target = Path.Combine(backupDirectory, Path.GetFileName(file));
|
||||
string fileName = Path.GetFileName(file);
|
||||
switch (fileName) {
|
||||
// Files that we want to keep in original directory
|
||||
case "NLog.config":
|
||||
continue;
|
||||
}
|
||||
|
||||
string target = Path.Combine(backupDirectory, fileName);
|
||||
File.Move(file, target);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
<AssemblyVersion>3.0.0.7</AssemblyVersion>
|
||||
<FileVersion>3.0.0.7</FileVersion>
|
||||
<AssemblyVersion>3.0.1.4</AssemblyVersion>
|
||||
<FileVersion>3.0.1.4</FileVersion>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<ErrorReport>none</ErrorReport>
|
||||
<ApplicationIcon>ASF.ico</ApplicationIcon>
|
||||
@@ -18,6 +18,8 @@
|
||||
<RepositoryUrl>https://github.com/JustArchi/ArchiSteamFarm.git</RepositoryUrl>
|
||||
<PackageIconUrl>https://github.com/JustArchi/ArchiSteamFarm/raw/master/resources/ASF.ico</PackageIconUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<ServerGarbageCollection>true</ServerGarbageCollection>
|
||||
<ConcurrentGarbageCollection>true</ConcurrentGarbageCollection>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
@@ -26,12 +28,13 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.5.2-beta3" />
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.5.2-beta6" />
|
||||
<PackageReference Include="Humanizer" Version="2.2.0" />
|
||||
<PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0-beta1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
|
||||
<PackageReference Include="NLog" Version="5.0.0-beta09" />
|
||||
<PackageReference Include="SteamKit2" Version="2.0.0-Alpha5" />
|
||||
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="4.4.0-preview2-25405-01" />
|
||||
<PackageReference Include="SteamKit2" Version="2.0.0-Alpha6" />
|
||||
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="4.4.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -58,15 +58,15 @@ namespace ArchiSteamFarm {
|
||||
|
||||
private const string SteamStoreURL = "http://" + SteamStoreHost;
|
||||
|
||||
private static readonly SemaphoreSlim InventorySemaphore = new SemaphoreSlim(1);
|
||||
private static readonly SemaphoreSlim InventorySemaphore = new SemaphoreSlim(1, 1);
|
||||
|
||||
private static int Timeout = GlobalConfig.DefaultConnectionTimeout * 1000; // This must be int type
|
||||
|
||||
private readonly SemaphoreSlim ApiKeySemaphore = new SemaphoreSlim(1);
|
||||
private readonly SemaphoreSlim ApiKeySemaphore = new SemaphoreSlim(1, 1);
|
||||
private readonly Bot Bot;
|
||||
private readonly SemaphoreSlim PublicInventorySemaphore = new SemaphoreSlim(1);
|
||||
private readonly SemaphoreSlim SessionSemaphore = new SemaphoreSlim(1);
|
||||
private readonly SemaphoreSlim TradeTokenSemaphore = new SemaphoreSlim(1);
|
||||
private readonly SemaphoreSlim PublicInventorySemaphore = new SemaphoreSlim(1, 1);
|
||||
private readonly SemaphoreSlim SessionSemaphore = new SemaphoreSlim(1, 1);
|
||||
private readonly SemaphoreSlim TradeTokenSemaphore = new SemaphoreSlim(1, 1);
|
||||
private readonly WebBrowser WebBrowser;
|
||||
|
||||
private string CachedApiKey;
|
||||
@@ -86,6 +86,7 @@ namespace ArchiSteamFarm {
|
||||
PublicInventorySemaphore.Dispose();
|
||||
SessionSemaphore.Dispose();
|
||||
TradeTokenSemaphore.Dispose();
|
||||
WebBrowser.Dispose();
|
||||
}
|
||||
|
||||
internal async Task<bool> AcceptTradeOffer(ulong tradeID) {
|
||||
@@ -482,7 +483,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
[SuppressMessage("ReSharper", "FunctionComplexityOverflow")]
|
||||
internal async Task<HashSet<Steam.Item>> GetMySteamInventory(bool tradable, HashSet<Steam.Item.EType> wantedTypes, HashSet<uint> wantedRealAppIDs = null) {
|
||||
internal async Task<HashSet<Steam.Item>> GetMySteamInventory(bool trading, HashSet<Steam.Item.EType> wantedTypes, HashSet<uint> wantedRealAppIDs = null) {
|
||||
if ((wantedTypes == null) || (wantedTypes.Count == 0)) {
|
||||
Bot.ArchiLogger.LogNullError(nameof(wantedTypes));
|
||||
return null;
|
||||
@@ -494,7 +495,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
HashSet<Steam.Item> result = new HashSet<Steam.Item>();
|
||||
|
||||
string request = SteamCommunityURL + "/my/inventory/json/" + Steam.Item.SteamAppID + "/" + Steam.Item.SteamCommunityContextID + "?l=english&trading=" + (tradable ? "1" : "0") + "&start=";
|
||||
string request = SteamCommunityURL + "/my/inventory/json/" + Steam.Item.SteamAppID + "/" + Steam.Item.SteamCommunityContextID + "?l=english" + (trading ? "&trading=1" : "") + "&start=";
|
||||
uint currentPage = 0;
|
||||
|
||||
await InventorySemaphore.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
@@ -50,12 +50,11 @@ namespace ArchiSteamFarm {
|
||||
private const ushort MaxSteamMessageLength = 2048;
|
||||
private const byte MaxTwoFactorCodeFailures = 3;
|
||||
private const byte MinHeartBeatTTL = GlobalConfig.DefaultConnectionTimeout; // Assume client is responsive for at least that amount of seconds
|
||||
private const byte PICSCooldownInMiliseconds = 200; // We might need to tune this further
|
||||
|
||||
internal static readonly ConcurrentDictionary<string, Bot> Bots = new ConcurrentDictionary<string, Bot>();
|
||||
|
||||
private static readonly SemaphoreSlim GiftsSemaphore = new SemaphoreSlim(1);
|
||||
private static readonly SemaphoreSlim LoginSemaphore = new SemaphoreSlim(1);
|
||||
private static readonly SemaphoreSlim GiftsSemaphore = new SemaphoreSlim(1, 1);
|
||||
private static readonly SemaphoreSlim LoginSemaphore = new SemaphoreSlim(1, 1);
|
||||
private static readonly SteamConfiguration SteamConfiguration = new SteamConfiguration();
|
||||
|
||||
internal readonly ArchiLogger ArchiLogger;
|
||||
@@ -74,16 +73,16 @@ namespace ArchiSteamFarm {
|
||||
private readonly BotDatabase BotDatabase;
|
||||
private readonly string BotName;
|
||||
private readonly CallbackManager CallbackManager;
|
||||
private readonly SemaphoreSlim CallbackSemaphore = new SemaphoreSlim(1);
|
||||
private readonly SemaphoreSlim CallbackSemaphore = new SemaphoreSlim(1, 1);
|
||||
|
||||
[JsonProperty]
|
||||
private readonly CardsFarmer CardsFarmer;
|
||||
|
||||
private readonly ConcurrentHashSet<ulong> HandledGifts = new ConcurrentHashSet<ulong>();
|
||||
private readonly Timer HeartBeatTimer;
|
||||
private readonly SemaphoreSlim InitializationSemaphore = new SemaphoreSlim(1);
|
||||
private readonly SemaphoreSlim LootingSemaphore = new SemaphoreSlim(1);
|
||||
private readonly SemaphoreSlim PICSSemaphore = new SemaphoreSlim(1);
|
||||
private readonly SemaphoreSlim InitializationSemaphore = new SemaphoreSlim(1, 1);
|
||||
private readonly SemaphoreSlim LootingSemaphore = new SemaphoreSlim(1, 1);
|
||||
private readonly SemaphoreSlim PICSSemaphore = new SemaphoreSlim(1, 1);
|
||||
private readonly Statistics Statistics;
|
||||
private readonly SteamApps SteamApps;
|
||||
private readonly SteamClient SteamClient;
|
||||
@@ -232,22 +231,24 @@ namespace ArchiSteamFarm {
|
||||
|
||||
public void Dispose() {
|
||||
// Those are objects that are always being created if constructor doesn't throw exception
|
||||
ArchiWebHandler.Dispose();
|
||||
CallbackSemaphore.Dispose();
|
||||
CardsFarmer.Dispose();
|
||||
HeartBeatTimer.Dispose();
|
||||
InitializationSemaphore.Dispose();
|
||||
LootingSemaphore.Dispose();
|
||||
PICSSemaphore.Dispose();
|
||||
Trading.Dispose();
|
||||
|
||||
// Those are objects that might be null and the check should be in-place
|
||||
ArchiWebHandler?.Dispose();
|
||||
BotDatabase?.Dispose();
|
||||
CardsFarmer?.Dispose();
|
||||
CardsFarmerResumeTimer?.Dispose();
|
||||
ConnectionFailureTimer?.Dispose();
|
||||
FamilySharingInactivityTimer?.Dispose();
|
||||
HeartBeatTimer?.Dispose();
|
||||
PlayingWasBlockedTimer?.Dispose();
|
||||
SendItemsTimer?.Dispose();
|
||||
Statistics?.Dispose();
|
||||
SteamSaleEvent?.Dispose();
|
||||
Trading?.Dispose();
|
||||
}
|
||||
|
||||
internal async Task<bool> AcceptConfirmations(bool accept, Steam.ConfirmationDetails.EType acceptedType = Steam.ConfirmationDetails.EType.Unknown, ulong acceptedSteamID = 0, HashSet<ulong> acceptedTradeIDs = null) {
|
||||
@@ -358,13 +359,10 @@ namespace ArchiSteamFarm {
|
||||
try {
|
||||
productInfoResultSet = await SteamApps.PICSGetProductInfo(appID, null, false);
|
||||
} catch (Exception e) {
|
||||
ArchiLogger.LogGenericException(e);
|
||||
ArchiLogger.LogGenericWarningException(e);
|
||||
return (0, DateTime.MinValue);
|
||||
} finally {
|
||||
Task.Run(async () => {
|
||||
await Task.Delay(PICSCooldownInMiliseconds).ConfigureAwait(false);
|
||||
PICSSemaphore.Release();
|
||||
}).Forget();
|
||||
PICSSemaphore.Release();
|
||||
}
|
||||
|
||||
// ReSharper disable once LoopCanBePartlyConvertedToQuery - C# 7.0 out can't be used within LINQ query yet | https://github.com/dotnet/roslyn/issues/15619
|
||||
@@ -468,13 +466,10 @@ namespace ArchiSteamFarm {
|
||||
try {
|
||||
productInfoResultSet = await SteamApps.PICSGetProductInfo(Enumerable.Empty<uint>(), packageIDs);
|
||||
} catch (Exception e) {
|
||||
ArchiLogger.LogGenericException(e);
|
||||
ArchiLogger.LogGenericWarningException(e);
|
||||
return null;
|
||||
} finally {
|
||||
Task.Run(async () => {
|
||||
await Task.Delay(PICSCooldownInMiliseconds).ConfigureAwait(false);
|
||||
PICSSemaphore.Release();
|
||||
}).Forget();
|
||||
PICSSemaphore.Release();
|
||||
}
|
||||
|
||||
Dictionary<uint, HashSet<uint>> result = new Dictionary<uint, HashSet<uint>>();
|
||||
@@ -674,7 +669,7 @@ namespace ArchiSteamFarm {
|
||||
try {
|
||||
callback = await SteamUser.RequestWebAPIUserNonce();
|
||||
} catch (Exception e) {
|
||||
ArchiLogger.LogGenericException(e);
|
||||
ArchiLogger.LogGenericWarningException(e);
|
||||
await Connect(true).ConfigureAwait(false);
|
||||
return false;
|
||||
}
|
||||
@@ -810,13 +805,13 @@ namespace ArchiSteamFarm {
|
||||
return await ResponseBlacklistAdd(steamID, args[1], args[2]).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return ResponseBlacklistAdd(steamID, args[1]);
|
||||
return await ResponseBlacklistAdd(steamID, args[1]).ConfigureAwait(false);
|
||||
case "!BLRM":
|
||||
if (args.Length > 2) {
|
||||
return await ResponseBlacklistRemove(steamID, args[1], args[2]).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return ResponseBlacklistRemove(steamID, args[1]);
|
||||
return await ResponseBlacklistRemove(steamID, args[1]).ConfigureAwait(false);
|
||||
case "!FARM":
|
||||
return await ResponseFarm(steamID, args[1]).ConfigureAwait(false);
|
||||
case "!INPUT":
|
||||
@@ -832,13 +827,13 @@ namespace ArchiSteamFarm {
|
||||
return await ResponseIdleQueueAdd(steamID, args[1], args[2]).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return ResponseIdleQueueAdd(steamID, args[1]);
|
||||
return await ResponseIdleQueueAdd(steamID, args[1]).ConfigureAwait(false);
|
||||
case "!IQRM":
|
||||
if (args.Length > 2) {
|
||||
return await ResponseIdleQueueRemove(steamID, args[1], args[2]).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return ResponseIdleQueueRemove(steamID, args[1]);
|
||||
return await ResponseIdleQueueRemove(steamID, args[1]).ConfigureAwait(false);
|
||||
case "!LOOT":
|
||||
return await ResponseLoot(steamID, args[1]).ConfigureAwait(false);
|
||||
case "!LOOT^":
|
||||
@@ -903,6 +898,16 @@ namespace ArchiSteamFarm {
|
||||
return await ResponseStatus(steamID, args[1]).ConfigureAwait(false);
|
||||
case "!STOP":
|
||||
return await ResponseStop(steamID, args[1]).ConfigureAwait(false);
|
||||
case "!TRANSFER":
|
||||
if (args.Length > 3) {
|
||||
return await ResponseTransfer(steamID, args[1], args[2], args[3]).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (args.Length > 2) {
|
||||
return await ResponseTransfer(steamID, args[1], args[2]).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return ResponseUnknown(steamID);
|
||||
case "!UNPACK":
|
||||
return await ResponseUnpackBoosters(steamID, args[1]).ConfigureAwait(false);
|
||||
default:
|
||||
@@ -1141,7 +1146,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
try {
|
||||
if (DateTime.UtcNow.Subtract(ArchiHandler.LastPacketReceived).TotalSeconds > MinHeartBeatTTL) {
|
||||
await SteamApps.PICSGetProductInfo(0, null);
|
||||
await SteamFriends.RequestProfileInfo(SteamClient.SteamID);
|
||||
}
|
||||
|
||||
HeartBeatFailures = 0;
|
||||
@@ -1163,7 +1168,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
private void ImportAuthenticator(string maFilePath) {
|
||||
private async Task ImportAuthenticator(string maFilePath) {
|
||||
if (HasMobileAuthenticator || !File.Exists(maFilePath)) {
|
||||
return;
|
||||
}
|
||||
@@ -1171,7 +1176,8 @@ namespace ArchiSteamFarm {
|
||||
ArchiLogger.LogGenericInfo(Strings.BotAuthenticatorConverting);
|
||||
|
||||
try {
|
||||
BotDatabase.MobileAuthenticator = JsonConvert.DeserializeObject<MobileAuthenticator>(File.ReadAllText(maFilePath));
|
||||
MobileAuthenticator authenticator = JsonConvert.DeserializeObject<MobileAuthenticator>(File.ReadAllText(maFilePath));
|
||||
await BotDatabase.SetMobileAuthenticator(authenticator).ConfigureAwait(false);
|
||||
File.Delete(maFilePath);
|
||||
} catch (Exception e) {
|
||||
ArchiLogger.LogGenericException(e);
|
||||
@@ -1190,14 +1196,14 @@ namespace ArchiSteamFarm {
|
||||
if (string.IsNullOrEmpty(DeviceID)) {
|
||||
string deviceID = Program.GetUserInput(ASF.EUserInputType.DeviceID, BotName);
|
||||
if (string.IsNullOrEmpty(deviceID)) {
|
||||
BotDatabase.MobileAuthenticator = null;
|
||||
await BotDatabase.SetMobileAuthenticator().ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
SetUserInput(ASF.EUserInputType.DeviceID, deviceID);
|
||||
}
|
||||
|
||||
BotDatabase.CorrectMobileAuthenticatorDeviceID(DeviceID);
|
||||
await BotDatabase.CorrectMobileAuthenticatorDeviceID(DeviceID).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
ArchiLogger.LogGenericInfo(Strings.BotAuthenticatorImportFinished);
|
||||
@@ -1283,7 +1289,7 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
ArchiLogger.LogGenericError(Strings.BotHeartBeatFailed);
|
||||
ArchiLogger.LogGenericWarning(Strings.BotHeartBeatFailed);
|
||||
Destroy(true);
|
||||
RegisterBot(BotName);
|
||||
}
|
||||
@@ -1433,7 +1439,7 @@ namespace ArchiSteamFarm {
|
||||
try {
|
||||
await SteamFriends.SetPersonaState(EPersonaState.Online);
|
||||
} catch (Exception e) {
|
||||
ArchiLogger.LogGenericException(e);
|
||||
ArchiLogger.LogGenericWarningException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1591,7 +1597,7 @@ namespace ArchiSteamFarm {
|
||||
goto case EResult.RateLimitExceeded;
|
||||
}
|
||||
|
||||
BotDatabase.LoginKey = null;
|
||||
await BotDatabase.SetLoginKey().ConfigureAwait(false);
|
||||
ArchiLogger.LogGenericInfo(Strings.BotRemovedExpiredLoginKey);
|
||||
break;
|
||||
case EResult.NoConnection:
|
||||
@@ -1828,15 +1834,15 @@ namespace ArchiSteamFarm {
|
||||
ArchiLogger.LogGenericWarning(Strings.BotAccountLocked);
|
||||
}
|
||||
|
||||
if (callback.CellID != 0) {
|
||||
Program.GlobalDatabase.CellID = callback.CellID;
|
||||
if ((callback.CellID != 0) && (callback.CellID != Program.GlobalDatabase.CellID)) {
|
||||
await Program.GlobalDatabase.SetCellID(callback.CellID).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (!HasMobileAuthenticator) {
|
||||
// Support and convert 2FA files
|
||||
string maFilePath = Path.Combine(SharedInfo.ConfigDirectory, callback.ClientSteamID.ConvertToUInt64() + ".maFile");
|
||||
if (File.Exists(maFilePath)) {
|
||||
ImportAuthenticator(maFilePath);
|
||||
await ImportAuthenticator(maFilePath).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1903,7 +1909,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
private void OnLoginKey(SteamUser.LoginKeyCallback callback) {
|
||||
private async void OnLoginKey(SteamUser.LoginKeyCallback callback) {
|
||||
if (string.IsNullOrEmpty(callback?.LoginKey)) {
|
||||
ArchiLogger.LogNullError(nameof(callback) + " || " + nameof(callback.LoginKey));
|
||||
return;
|
||||
@@ -1914,7 +1920,7 @@ namespace ArchiSteamFarm {
|
||||
loginKey = CryptoHelper.Encrypt(BotConfig.PasswordFormat, loginKey);
|
||||
}
|
||||
|
||||
BotDatabase.LoginKey = loginKey;
|
||||
await BotDatabase.SetLoginKey(loginKey).ConfigureAwait(false);
|
||||
SteamUser.AcceptNewLoginKey(callback);
|
||||
}
|
||||
|
||||
@@ -2210,7 +2216,7 @@ namespace ArchiSteamFarm {
|
||||
try {
|
||||
callback = await SteamApps.RequestFreeLicense(gameID);
|
||||
} catch (Exception e) {
|
||||
ArchiLogger.LogGenericException(e);
|
||||
ArchiLogger.LogGenericWarningException(e);
|
||||
response.Append(FormatBotResponse(string.Format(Strings.BotAddLicense, gameID, EResult.Timeout)));
|
||||
break;
|
||||
}
|
||||
@@ -2313,7 +2319,7 @@ namespace ArchiSteamFarm {
|
||||
foreach (string flag in flags) {
|
||||
switch (flag.ToUpperInvariant()) {
|
||||
case "FD":
|
||||
redeemFlags |= ERedeemFlags.ForceDistribution;
|
||||
redeemFlags |= ERedeemFlags.ForceDistributing;
|
||||
break;
|
||||
case "FF":
|
||||
redeemFlags |= ERedeemFlags.ForceForwarding;
|
||||
@@ -2322,7 +2328,7 @@ namespace ArchiSteamFarm {
|
||||
redeemFlags |= ERedeemFlags.ForceKeepMissingGames;
|
||||
break;
|
||||
case "SD":
|
||||
redeemFlags |= ERedeemFlags.SkipDistribution;
|
||||
redeemFlags |= ERedeemFlags.SkipDistributing;
|
||||
break;
|
||||
case "SF":
|
||||
redeemFlags |= ERedeemFlags.SkipForwarding;
|
||||
@@ -2438,7 +2444,7 @@ namespace ArchiSteamFarm {
|
||||
return null;
|
||||
}
|
||||
|
||||
private string ResponseBlacklistAdd(ulong steamID, string targetsText) {
|
||||
private async Task<string> ResponseBlacklistAdd(ulong steamID, string targetsText) {
|
||||
if ((steamID == 0) || string.IsNullOrEmpty(targetsText)) {
|
||||
ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(targetsText));
|
||||
return null;
|
||||
@@ -2463,7 +2469,7 @@ namespace ArchiSteamFarm {
|
||||
return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(targetIDs)));
|
||||
}
|
||||
|
||||
BotDatabase.AddBlacklistedFromTradesSteamIDs(targetIDs);
|
||||
await BotDatabase.AddBlacklistedFromTradesSteamIDs(targetIDs).ConfigureAwait(false);
|
||||
return FormatBotResponse(Strings.Done);
|
||||
}
|
||||
|
||||
@@ -2478,7 +2484,7 @@ namespace ArchiSteamFarm {
|
||||
return IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null;
|
||||
}
|
||||
|
||||
IEnumerable<Task<string>> tasks = bots.Select(bot => Task.Run(() => bot.ResponseBlacklistAdd(steamID, targetsText)));
|
||||
IEnumerable<Task<string>> tasks = bots.Select(bot => bot.ResponseBlacklistAdd(steamID, targetsText));
|
||||
ICollection<string> results;
|
||||
|
||||
switch (Program.GlobalConfig.OptimizationMode) {
|
||||
@@ -2509,7 +2515,7 @@ namespace ArchiSteamFarm {
|
||||
return IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null;
|
||||
}
|
||||
|
||||
IEnumerable<Task<string>> tasks = bots.Select(bot => Task.Run(() => bot.ResponseBlacklistRemove(steamID, targetsText)));
|
||||
IEnumerable<Task<string>> tasks = bots.Select(bot => bot.ResponseBlacklistRemove(steamID, targetsText));
|
||||
ICollection<string> results;
|
||||
|
||||
switch (Program.GlobalConfig.OptimizationMode) {
|
||||
@@ -2529,7 +2535,7 @@ namespace ArchiSteamFarm {
|
||||
return responses.Count > 0 ? string.Join("", responses) : null;
|
||||
}
|
||||
|
||||
private string ResponseBlacklistRemove(ulong steamID, string targetsText) {
|
||||
private async Task<string> ResponseBlacklistRemove(ulong steamID, string targetsText) {
|
||||
if ((steamID == 0) || string.IsNullOrEmpty(targetsText)) {
|
||||
ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(targetsText));
|
||||
return null;
|
||||
@@ -2554,7 +2560,7 @@ namespace ArchiSteamFarm {
|
||||
return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(targetIDs)));
|
||||
}
|
||||
|
||||
BotDatabase.RemoveBlacklistedFromTradesSteamIDs(targetIDs);
|
||||
await BotDatabase.RemoveBlacklistedFromTradesSteamIDs(targetIDs).ConfigureAwait(false);
|
||||
return FormatBotResponse(Strings.Done);
|
||||
}
|
||||
|
||||
@@ -2678,7 +2684,7 @@ namespace ArchiSteamFarm {
|
||||
return result;
|
||||
}
|
||||
|
||||
private string ResponseIdleQueueAdd(ulong steamID, string targetsText) {
|
||||
private async Task<string> ResponseIdleQueueAdd(ulong steamID, string targetsText) {
|
||||
if ((steamID == 0) || string.IsNullOrEmpty(targetsText)) {
|
||||
ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(targetsText));
|
||||
return null;
|
||||
@@ -2703,7 +2709,7 @@ namespace ArchiSteamFarm {
|
||||
return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(appIDs)));
|
||||
}
|
||||
|
||||
BotDatabase.AddIdlingPriorityAppIDs(appIDs);
|
||||
await BotDatabase.AddIdlingPriorityAppIDs(appIDs).ConfigureAwait(false);
|
||||
return FormatBotResponse(Strings.Done);
|
||||
}
|
||||
|
||||
@@ -2718,7 +2724,7 @@ namespace ArchiSteamFarm {
|
||||
return IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null;
|
||||
}
|
||||
|
||||
IEnumerable<Task<string>> tasks = bots.Select(bot => Task.Run(() => bot.ResponseIdleQueueAdd(steamID, targetsText)));
|
||||
IEnumerable<Task<string>> tasks = bots.Select(bot => bot.ResponseIdleQueueAdd(steamID, targetsText));
|
||||
ICollection<string> results;
|
||||
|
||||
switch (Program.GlobalConfig.OptimizationMode) {
|
||||
@@ -2749,7 +2755,7 @@ namespace ArchiSteamFarm {
|
||||
return IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null;
|
||||
}
|
||||
|
||||
IEnumerable<Task<string>> tasks = bots.Select(bot => Task.Run(() => bot.ResponseIdleQueueRemove(steamID, targetsText)));
|
||||
IEnumerable<Task<string>> tasks = bots.Select(bot => bot.ResponseIdleQueueRemove(steamID, targetsText));
|
||||
ICollection<string> results;
|
||||
|
||||
switch (Program.GlobalConfig.OptimizationMode) {
|
||||
@@ -2769,7 +2775,7 @@ namespace ArchiSteamFarm {
|
||||
return responses.Count > 0 ? string.Join("", responses) : null;
|
||||
}
|
||||
|
||||
private string ResponseIdleQueueRemove(ulong steamID, string targetsText) {
|
||||
private async Task<string> ResponseIdleQueueRemove(ulong steamID, string targetsText) {
|
||||
if ((steamID == 0) || string.IsNullOrEmpty(targetsText)) {
|
||||
ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(targetsText));
|
||||
return null;
|
||||
@@ -2794,7 +2800,7 @@ namespace ArchiSteamFarm {
|
||||
return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(appIDs)));
|
||||
}
|
||||
|
||||
BotDatabase.RemoveIdlingPriorityAppIDs(appIDs);
|
||||
await BotDatabase.RemoveIdlingPriorityAppIDs(appIDs).ConfigureAwait(false);
|
||||
return FormatBotResponse(Strings.Done);
|
||||
}
|
||||
|
||||
@@ -3011,7 +3017,7 @@ namespace ArchiSteamFarm {
|
||||
try {
|
||||
result = await SteamFriends.SetPersonaName(nickname);
|
||||
} catch (Exception e) {
|
||||
ArchiLogger.LogGenericException(e);
|
||||
ArchiLogger.LogGenericWarningException(e);
|
||||
return FormatBotResponse(Strings.WarningFailed);
|
||||
}
|
||||
|
||||
@@ -3403,7 +3409,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
bool forward = !redeemFlags.HasFlag(ERedeemFlags.SkipForwarding) && (redeemFlags.HasFlag(ERedeemFlags.ForceForwarding) || BotConfig.RedeemingPreferences.HasFlag(BotConfig.ERedeemingPreferences.Forwarding));
|
||||
bool distribute = !redeemFlags.HasFlag(ERedeemFlags.SkipDistribution) && (redeemFlags.HasFlag(ERedeemFlags.ForceDistribution) || BotConfig.RedeemingPreferences.HasFlag(BotConfig.ERedeemingPreferences.Distributing));
|
||||
bool distribute = !redeemFlags.HasFlag(ERedeemFlags.SkipDistributing) && (redeemFlags.HasFlag(ERedeemFlags.ForceDistributing) || BotConfig.RedeemingPreferences.HasFlag(BotConfig.ERedeemingPreferences.Distributing));
|
||||
bool keepMissingGames = !redeemFlags.HasFlag(ERedeemFlags.SkipKeepMissingGames) && (redeemFlags.HasFlag(ERedeemFlags.ForceKeepMissingGames) || BotConfig.RedeemingPreferences.HasFlag(BotConfig.ERedeemingPreferences.KeepMissingGames));
|
||||
|
||||
string[] keysList = keys.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
@@ -3916,6 +3922,153 @@ namespace ArchiSteamFarm {
|
||||
return responses.Count > 0 ? string.Join("", responses) : null;
|
||||
}
|
||||
|
||||
private async Task<string> ResponseTransfer(ulong steamID, string mode, string botNameTo) {
|
||||
if ((steamID == 0) || string.IsNullOrEmpty(botNameTo) || string.IsNullOrEmpty(mode)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(mode) + " || " + nameof(botNameTo));
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!IsMaster(steamID)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!IsConnectedAndLoggedOn) {
|
||||
return FormatBotResponse(Strings.BotNotConnected);
|
||||
}
|
||||
|
||||
if (!LootingAllowed) {
|
||||
return FormatBotResponse(Strings.BotLootingTemporarilyDisabled);
|
||||
}
|
||||
|
||||
if (!Bots.TryGetValue(botNameTo, out Bot targetBot)) {
|
||||
return IsOwner(steamID) ? FormatBotResponse(string.Format(Strings.BotNotFound, botNameTo)) : null;
|
||||
}
|
||||
|
||||
if (targetBot.SteamID == 0) {
|
||||
return FormatBotResponse(Strings.BotNotConnected);
|
||||
}
|
||||
|
||||
if (targetBot.SteamID == SteamID) {
|
||||
return FormatBotResponse(Strings.BotLootingYourself);
|
||||
}
|
||||
|
||||
HashSet<Steam.Item.EType> transferTypes = new HashSet<Steam.Item.EType>();
|
||||
|
||||
string[] modes = mode.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (string singleMode in modes) {
|
||||
switch (singleMode.ToUpper()) {
|
||||
case "A":
|
||||
case "ALL":
|
||||
foreach (Steam.Item.EType type in Enum.GetValues(typeof(Steam.Item.EType))) {
|
||||
transferTypes.Add(type);
|
||||
}
|
||||
|
||||
break;
|
||||
case "BG":
|
||||
case "BACKGROUND":
|
||||
transferTypes.Add(Steam.Item.EType.ProfileBackground);
|
||||
break;
|
||||
case "BO":
|
||||
case "BOOSTER":
|
||||
transferTypes.Add(Steam.Item.EType.BoosterPack);
|
||||
break;
|
||||
case "C":
|
||||
case "CARD":
|
||||
transferTypes.Add(Steam.Item.EType.TradingCard);
|
||||
break;
|
||||
case "E":
|
||||
case "EMOTICON":
|
||||
transferTypes.Add(Steam.Item.EType.Emoticon);
|
||||
break;
|
||||
case "F":
|
||||
case "FOIL":
|
||||
transferTypes.Add(Steam.Item.EType.FoilTradingCard);
|
||||
break;
|
||||
case "G":
|
||||
case "GEMS":
|
||||
transferTypes.Add(Steam.Item.EType.SteamGems);
|
||||
break;
|
||||
case "U":
|
||||
case "UNKNOWN":
|
||||
transferTypes.Add(Steam.Item.EType.Unknown);
|
||||
break;
|
||||
default:
|
||||
return FormatBotResponse(string.Format(Strings.ErrorIsInvalid, mode));
|
||||
}
|
||||
}
|
||||
|
||||
if (!LootingSemaphore.Wait(0)) {
|
||||
return FormatBotResponse(Strings.BotLootingFailed);
|
||||
}
|
||||
|
||||
try {
|
||||
HashSet<Steam.Item> inventory = await ArchiWebHandler.GetMySteamInventory(true, transferTypes).ConfigureAwait(false);
|
||||
if ((inventory == null) || (inventory.Count == 0)) {
|
||||
return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(inventory)));
|
||||
}
|
||||
|
||||
string tradeToken = null;
|
||||
|
||||
if (SteamFriends.GetFriendRelationship(targetBot.SteamID) != EFriendRelationship.Friend) {
|
||||
tradeToken = await targetBot.ArchiWebHandler.GetTradeToken().ConfigureAwait(false);
|
||||
if (string.IsNullOrEmpty(tradeToken)) {
|
||||
return FormatBotResponse(Strings.BotLootingFailed);
|
||||
}
|
||||
}
|
||||
|
||||
if (!await ArchiWebHandler.MarkSentTrades().ConfigureAwait(false)) {
|
||||
return FormatBotResponse(Strings.BotLootingFailed);
|
||||
}
|
||||
|
||||
if (!await ArchiWebHandler.SendTradeOffer(inventory, targetBot.SteamID, tradeToken).ConfigureAwait(false)) {
|
||||
return FormatBotResponse(Strings.BotLootingFailed);
|
||||
}
|
||||
|
||||
if (HasMobileAuthenticator) {
|
||||
// Give Steam network some time to generate confirmations
|
||||
await Task.Delay(3000).ConfigureAwait(false);
|
||||
if (!await AcceptConfirmations(true, Steam.ConfirmationDetails.EType.Trade, targetBot.SteamID).ConfigureAwait(false)) {
|
||||
return FormatBotResponse(Strings.BotLootingFailed);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
LootingSemaphore.Release();
|
||||
}
|
||||
|
||||
return FormatBotResponse(Strings.BotLootingSuccess);
|
||||
}
|
||||
|
||||
private static async Task<string> ResponseTransfer(ulong steamID, string botNames, string mode, string botNameTo) {
|
||||
if ((steamID == 0) || string.IsNullOrEmpty(botNames) || string.IsNullOrEmpty(mode) || string.IsNullOrEmpty(botNameTo)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames) + " || " + nameof(mode) + " || " + nameof(botNameTo));
|
||||
return null;
|
||||
}
|
||||
|
||||
HashSet<Bot> bots = GetBots(botNames);
|
||||
if ((bots == null) || (bots.Count == 0)) {
|
||||
return IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null;
|
||||
}
|
||||
|
||||
IEnumerable<Task<string>> tasks = bots.Select(bot => bot.ResponseTransfer(steamID, mode, botNameTo));
|
||||
ICollection<string> results;
|
||||
|
||||
switch (Program.GlobalConfig.OptimizationMode) {
|
||||
case GlobalConfig.EOptimizationMode.MinMemoryUsage:
|
||||
results = new List<string>(bots.Count);
|
||||
foreach (Task<string> task in tasks) {
|
||||
results.Add(await task.ConfigureAwait(false));
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
results = await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
|
||||
List<string> responses = new List<string>(results.Where(result => !string.IsNullOrEmpty(result)));
|
||||
return responses.Count > 0 ? string.Join("", responses) : null;
|
||||
}
|
||||
|
||||
private string ResponseUnknown(ulong steamID) {
|
||||
if (steamID != 0) {
|
||||
return IsOperator(steamID) ? FormatBotResponse(Strings.UnknownCommand) : null;
|
||||
@@ -4111,7 +4264,7 @@ namespace ArchiSteamFarm {
|
||||
if (!HasMobileAuthenticator) {
|
||||
string maFilePath = BotPath + ".maFile";
|
||||
if (File.Exists(maFilePath)) {
|
||||
ImportAuthenticator(maFilePath);
|
||||
await ImportAuthenticator(maFilePath).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4164,8 +4317,8 @@ namespace ArchiSteamFarm {
|
||||
Validate = 1,
|
||||
ForceForwarding = 2,
|
||||
SkipForwarding = 4,
|
||||
ForceDistribution = 8,
|
||||
SkipDistribution = 16,
|
||||
ForceDistributing = 8,
|
||||
SkipDistributing = 16,
|
||||
SkipInitial = 32,
|
||||
ForceKeepMissingGames = 64,
|
||||
SkipKeepMissingGames = 128
|
||||
|
||||
@@ -172,6 +172,11 @@ namespace ArchiSteamFarm {
|
||||
// This constructor is used only by deserializer
|
||||
private BotConfig() { }
|
||||
|
||||
// Functions below are used for skipping serialization of sensitive fields in API response
|
||||
public bool ShouldSerializeSteamLogin() => false;
|
||||
public bool ShouldSerializeSteamParentalPIN() => false;
|
||||
public bool ShouldSerializeSteamPassword() => false;
|
||||
|
||||
internal static BotConfig Load(string filePath) {
|
||||
if (string.IsNullOrEmpty(filePath)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(filePath));
|
||||
|
||||
@@ -26,49 +26,25 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal sealed class BotDatabase {
|
||||
internal sealed class BotDatabase : IDisposable {
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
private readonly ConcurrentHashSet<ulong> BlacklistedFromTradesSteamIDs = new ConcurrentHashSet<ulong>();
|
||||
|
||||
private readonly object FileLock = new object();
|
||||
private readonly SemaphoreSlim FileSemaphore = new SemaphoreSlim(1, 1);
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
private readonly ConcurrentHashSet<uint> IdlingPriorityAppIDs = new ConcurrentHashSet<uint>();
|
||||
|
||||
internal string LoginKey {
|
||||
get => _LoginKey;
|
||||
[JsonProperty(PropertyName = "_LoginKey")]
|
||||
internal string LoginKey { get; private set; }
|
||||
|
||||
set {
|
||||
if (_LoginKey == value) {
|
||||
return;
|
||||
}
|
||||
|
||||
_LoginKey = value;
|
||||
Save();
|
||||
}
|
||||
}
|
||||
|
||||
internal MobileAuthenticator MobileAuthenticator {
|
||||
get => _MobileAuthenticator;
|
||||
|
||||
set {
|
||||
if (_MobileAuthenticator == value) {
|
||||
return;
|
||||
}
|
||||
|
||||
_MobileAuthenticator = value;
|
||||
Save();
|
||||
}
|
||||
}
|
||||
|
||||
[JsonProperty]
|
||||
private string _LoginKey;
|
||||
|
||||
[JsonProperty]
|
||||
private MobileAuthenticator _MobileAuthenticator;
|
||||
[JsonProperty(PropertyName = "_MobileAuthenticator")]
|
||||
internal MobileAuthenticator MobileAuthenticator { get; private set; }
|
||||
|
||||
private string FilePath;
|
||||
|
||||
@@ -79,43 +55,51 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
FilePath = filePath;
|
||||
Save();
|
||||
Save().Wait();
|
||||
}
|
||||
|
||||
// This constructor is used only by deserializer
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Local")]
|
||||
private BotDatabase() { }
|
||||
|
||||
internal void AddBlacklistedFromTradesSteamIDs(HashSet<ulong> steamIDs) {
|
||||
public void Dispose() {
|
||||
// Those are objects that are always being created if constructor doesn't throw exception
|
||||
FileSemaphore.Dispose();
|
||||
|
||||
// Those are objects that might be null and the check should be in-place
|
||||
MobileAuthenticator?.Dispose();
|
||||
}
|
||||
|
||||
internal async Task AddBlacklistedFromTradesSteamIDs(HashSet<ulong> steamIDs) {
|
||||
if ((steamIDs == null) || (steamIDs.Count == 0)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(steamIDs));
|
||||
return;
|
||||
}
|
||||
|
||||
if (BlacklistedFromTradesSteamIDs.AddRange(steamIDs)) {
|
||||
Save();
|
||||
await Save().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
internal void AddIdlingPriorityAppIDs(HashSet<uint> appIDs) {
|
||||
internal async Task AddIdlingPriorityAppIDs(HashSet<uint> appIDs) {
|
||||
if ((appIDs == null) || (appIDs.Count == 0)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(appIDs));
|
||||
return;
|
||||
}
|
||||
|
||||
if (IdlingPriorityAppIDs.AddRange(appIDs)) {
|
||||
Save();
|
||||
await Save().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
internal void CorrectMobileAuthenticatorDeviceID(string deviceID) {
|
||||
internal async Task CorrectMobileAuthenticatorDeviceID(string deviceID) {
|
||||
if (string.IsNullOrEmpty(deviceID) || (MobileAuthenticator == null)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(deviceID) + " || " + nameof(MobileAuthenticator));
|
||||
return;
|
||||
}
|
||||
|
||||
if (MobileAuthenticator.CorrectDeviceID(deviceID)) {
|
||||
Save();
|
||||
await Save().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,49 +154,69 @@ namespace ArchiSteamFarm {
|
||||
return botDatabase;
|
||||
}
|
||||
|
||||
internal void RemoveBlacklistedFromTradesSteamIDs(HashSet<ulong> steamIDs) {
|
||||
internal async Task RemoveBlacklistedFromTradesSteamIDs(HashSet<ulong> steamIDs) {
|
||||
if ((steamIDs == null) || (steamIDs.Count == 0)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(steamIDs));
|
||||
return;
|
||||
}
|
||||
|
||||
if (BlacklistedFromTradesSteamIDs.RemoveRange(steamIDs)) {
|
||||
Save();
|
||||
await Save().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
internal void RemoveIdlingPriorityAppIDs(HashSet<uint> appIDs) {
|
||||
internal async Task RemoveIdlingPriorityAppIDs(HashSet<uint> appIDs) {
|
||||
if ((appIDs == null) || (appIDs.Count == 0)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(appIDs));
|
||||
return;
|
||||
}
|
||||
|
||||
if (IdlingPriorityAppIDs.RemoveRange(appIDs)) {
|
||||
Save();
|
||||
await Save().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void Save() {
|
||||
internal async Task SetLoginKey(string value = null) {
|
||||
if (value == LoginKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
LoginKey = value;
|
||||
await Save().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
internal async Task SetMobileAuthenticator(MobileAuthenticator value = null) {
|
||||
if (value == MobileAuthenticator) {
|
||||
return;
|
||||
}
|
||||
|
||||
MobileAuthenticator = value;
|
||||
await Save().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task Save() {
|
||||
string json = JsonConvert.SerializeObject(this);
|
||||
if (string.IsNullOrEmpty(json)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(json));
|
||||
return;
|
||||
}
|
||||
|
||||
lock (FileLock) {
|
||||
string newFilePath = FilePath + ".new";
|
||||
string newFilePath = FilePath + ".new";
|
||||
|
||||
try {
|
||||
File.WriteAllText(newFilePath, json);
|
||||
await FileSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
if (File.Exists(FilePath)) {
|
||||
File.Replace(newFilePath, FilePath, null);
|
||||
} else {
|
||||
File.Move(newFilePath, FilePath);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
ASF.ArchiLogger.LogGenericException(e);
|
||||
try {
|
||||
await File.WriteAllTextAsync(newFilePath, json).ConfigureAwait(false);
|
||||
|
||||
if (File.Exists(FilePath)) {
|
||||
File.Replace(newFilePath, FilePath, null);
|
||||
} else {
|
||||
File.Move(newFilePath, FilePath);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
ASF.ArchiLogger.LogGenericException(e);
|
||||
} finally {
|
||||
FileSemaphore.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,9 +58,9 @@ namespace ArchiSteamFarm {
|
||||
);
|
||||
|
||||
private readonly Bot Bot;
|
||||
private readonly SemaphoreSlim EventSemaphore = new SemaphoreSlim(1);
|
||||
private readonly SemaphoreSlim FarmingSemaphore = new SemaphoreSlim(1);
|
||||
private readonly ManualResetEventSlim FarmResetEvent = new ManualResetEventSlim(false);
|
||||
private readonly SemaphoreSlim EventSemaphore = new SemaphoreSlim(1, 1);
|
||||
private readonly SemaphoreSlim FarmingInitializationSemaphore = new SemaphoreSlim(1, 1);
|
||||
private readonly SemaphoreSlim FarmingResetSemaphore = new SemaphoreSlim(0, 1);
|
||||
private readonly Timer IdleFarmingTimer;
|
||||
|
||||
[JsonProperty]
|
||||
@@ -87,8 +87,9 @@ namespace ArchiSteamFarm {
|
||||
public void Dispose() {
|
||||
// Those are objects that are always being created if constructor doesn't throw exception
|
||||
EventSemaphore.Dispose();
|
||||
FarmingSemaphore.Dispose();
|
||||
FarmResetEvent.Dispose();
|
||||
FarmingInitializationSemaphore.Dispose();
|
||||
FarmingResetSemaphore.Dispose();
|
||||
GamesToFarm.Dispose();
|
||||
|
||||
// Those are objects that might be null and the check should be in-place
|
||||
IdleFarmingTimer?.Dispose();
|
||||
@@ -140,8 +141,19 @@ namespace ArchiSteamFarm {
|
||||
|
||||
internal async Task OnNewItemsNotification() {
|
||||
if (NowFarming) {
|
||||
FarmResetEvent.Set();
|
||||
return;
|
||||
await FarmingInitializationSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
try {
|
||||
if (NowFarming) {
|
||||
if (FarmingResetSemaphore.CurrentCount == 0) {
|
||||
FarmingResetSemaphore.Release();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
} finally {
|
||||
FarmingInitializationSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
// If we're not farming, and we got new items, it's likely to be a booster pack or likewise
|
||||
@@ -188,7 +200,7 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
await FarmingSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
await FarmingInitializationSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
try {
|
||||
if (NowFarming || Paused || !Bot.IsPlayingPossible) {
|
||||
@@ -231,7 +243,7 @@ namespace ArchiSteamFarm {
|
||||
KeepFarming = NowFarming = true;
|
||||
Utilities.StartBackgroundFunction(Farm);
|
||||
} finally {
|
||||
FarmingSemaphore.Release();
|
||||
FarmingInitializationSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,7 +252,7 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
await FarmingSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
await FarmingInitializationSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
try {
|
||||
if (!NowFarming) {
|
||||
@@ -248,7 +260,10 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
KeepFarming = false;
|
||||
FarmResetEvent.Set();
|
||||
|
||||
if (FarmingResetSemaphore.CurrentCount == 0) {
|
||||
FarmingResetSemaphore.Release();
|
||||
}
|
||||
|
||||
for (byte i = 0; (i < 5) && NowFarming; i++) {
|
||||
await Task.Delay(1000).ConfigureAwait(false);
|
||||
@@ -261,7 +276,7 @@ namespace ArchiSteamFarm {
|
||||
Bot.ArchiLogger.LogGenericInfo(Strings.IdlingStopped);
|
||||
Bot.OnFarmingStopped();
|
||||
} finally {
|
||||
FarmingSemaphore.Release();
|
||||
FarmingInitializationSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -334,7 +349,7 @@ namespace ArchiSteamFarm {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (GlobalConfig.GlobalBlacklist.Contains(appID) || Program.GlobalConfig.Blacklist.Contains(appID)) {
|
||||
if (GlobalConfig.GamesBlacklist.Contains(appID) || GlobalConfig.SalesBlacklist.Contains(appID) || Program.GlobalConfig.Blacklist.Contains(appID)) {
|
||||
// We have this appID blacklisted, so skip it
|
||||
continue;
|
||||
}
|
||||
@@ -556,65 +571,68 @@ namespace ArchiSteamFarm {
|
||||
// If we have restricted card drops, we use complex algorithm
|
||||
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.ChosenFarmingAlgorithm, "Complex"));
|
||||
while (GamesToFarm.Count > 0) {
|
||||
HashSet<Game> playableGamesToFarmSolo = new HashSet<Game>();
|
||||
foreach (Game game in GamesToFarm.Where(game => game.HoursPlayed >= HoursToBump)) {
|
||||
if (await IsPlayableGame(game).ConfigureAwait(false)) {
|
||||
playableGamesToFarmSolo.Add(game);
|
||||
HashSet<Game> gamesToCheck = new HashSet<Game>(GamesToFarm.Where(game => game.HoursPlayed >= HoursToBump));
|
||||
|
||||
foreach (Game game in gamesToCheck) {
|
||||
if (!await IsPlayableGame(game).ConfigureAwait(false)) {
|
||||
GamesToFarm.Remove(game);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (await FarmSolo(game).ConfigureAwait(false)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
NowFarming = false;
|
||||
return;
|
||||
}
|
||||
|
||||
gamesToCheck = new HashSet<Game>(GamesToFarm.OrderByDescending(game => game.HoursPlayed));
|
||||
HashSet<Game> playableGamesToFarmMultiple = new HashSet<Game>();
|
||||
|
||||
foreach (Game game in gamesToCheck) {
|
||||
if (!await IsPlayableGame(game).ConfigureAwait(false)) {
|
||||
GamesToFarm.Remove(game);
|
||||
continue;
|
||||
}
|
||||
|
||||
playableGamesToFarmMultiple.Add(game);
|
||||
if (playableGamesToFarmMultiple.Count >= ArchiHandler.MaxGamesPlayedConcurrently) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (playableGamesToFarmSolo.Count > 0) {
|
||||
while (playableGamesToFarmSolo.Count > 0) {
|
||||
Game playableGame = playableGamesToFarmSolo.First();
|
||||
if (await FarmSolo(playableGame).ConfigureAwait(false)) {
|
||||
playableGamesToFarmSolo.Remove(playableGame);
|
||||
} else {
|
||||
NowFarming = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (playableGamesToFarmMultiple.Count == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (await FarmMultiple(playableGamesToFarmMultiple).ConfigureAwait(false)) {
|
||||
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.IdlingFinishedForGames, string.Join(", ", playableGamesToFarmMultiple.Select(game => game.AppID))));
|
||||
} else {
|
||||
HashSet<Game> playableGamesToFarmMultiple = new HashSet<Game>();
|
||||
foreach (Game game in GamesToFarm.Where(game => game.HoursPlayed < HoursToBump).OrderByDescending(game => game.HoursPlayed)) {
|
||||
if (await IsPlayableGame(game).ConfigureAwait(false)) {
|
||||
playableGamesToFarmMultiple.Add(game);
|
||||
}
|
||||
|
||||
if (playableGamesToFarmMultiple.Count >= ArchiHandler.MaxGamesPlayedConcurrently) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (FarmMultiple(playableGamesToFarmMultiple)) {
|
||||
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.IdlingFinishedForGames, string.Join(", ", playableGamesToFarmMultiple.Select(game => game.AppID))));
|
||||
} else {
|
||||
NowFarming = false;
|
||||
return;
|
||||
}
|
||||
NowFarming = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If we have unrestricted card drops, we use simple algorithm
|
||||
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.ChosenFarmingAlgorithm, "Simple"));
|
||||
|
||||
while (GamesToFarm.Count > 0) {
|
||||
Game playableGame = null;
|
||||
foreach (Game game in GamesToFarm) {
|
||||
HashSet<Game> gamesToCheck = new HashSet<Game>(GamesToFarm);
|
||||
|
||||
foreach (Game game in gamesToCheck) {
|
||||
if (!await IsPlayableGame(game).ConfigureAwait(false)) {
|
||||
GamesToFarm.Remove(game);
|
||||
continue;
|
||||
}
|
||||
|
||||
playableGame = game;
|
||||
break;
|
||||
}
|
||||
|
||||
if (playableGame != null) {
|
||||
if (await FarmSolo(playableGame).ConfigureAwait(false)) {
|
||||
if (await FarmSolo(game).ConfigureAwait(false)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
NowFarming = false;
|
||||
return;
|
||||
NowFarming = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
} while ((await IsAnythingToFarm().ConfigureAwait(false)).GetValueOrDefault());
|
||||
@@ -646,8 +664,7 @@ namespace ArchiSteamFarm {
|
||||
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.StillIdling, game.AppID, game.GameName));
|
||||
|
||||
DateTime startFarmingPeriod = DateTime.UtcNow;
|
||||
if (FarmResetEvent.Wait(60 * 1000 * Program.GlobalConfig.FarmingDelay)) {
|
||||
FarmResetEvent.Reset();
|
||||
if (await FarmingResetSemaphore.WaitAsync(60 * 1000 * Program.GlobalConfig.FarmingDelay).ConfigureAwait(false)) {
|
||||
success = KeepFarming;
|
||||
}
|
||||
|
||||
@@ -665,7 +682,7 @@ namespace ArchiSteamFarm {
|
||||
return success;
|
||||
}
|
||||
|
||||
private bool FarmHours(ConcurrentHashSet<Game> games) {
|
||||
private async Task<bool> FarmHours(ConcurrentHashSet<Game> games) {
|
||||
if ((games == null) || (games.Count == 0)) {
|
||||
Bot.ArchiLogger.LogNullError(nameof(games));
|
||||
return false;
|
||||
@@ -689,8 +706,7 @@ namespace ArchiSteamFarm {
|
||||
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.StillIdlingList, string.Join(", ", games.Select(game => game.AppID))));
|
||||
|
||||
DateTime startFarmingPeriod = DateTime.UtcNow;
|
||||
if (FarmResetEvent.Wait(60 * 1000 * Program.GlobalConfig.FarmingDelay)) {
|
||||
FarmResetEvent.Reset();
|
||||
if (await FarmingResetSemaphore.WaitAsync(60 * 1000 * Program.GlobalConfig.FarmingDelay).ConfigureAwait(false)) {
|
||||
success = KeepFarming;
|
||||
}
|
||||
|
||||
@@ -711,7 +727,7 @@ namespace ArchiSteamFarm {
|
||||
return success;
|
||||
}
|
||||
|
||||
private bool FarmMultiple(IEnumerable<Game> games) {
|
||||
private async Task<bool> FarmMultiple(IEnumerable<Game> games) {
|
||||
if (games == null) {
|
||||
Bot.ArchiLogger.LogNullError(nameof(games));
|
||||
return false;
|
||||
@@ -721,7 +737,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.NowIdlingList, string.Join(", ", CurrentGamesFarming.Select(game => game.AppID))));
|
||||
|
||||
bool result = FarmHours(CurrentGamesFarming);
|
||||
bool result = await FarmHours(CurrentGamesFarming).ConfigureAwait(false);
|
||||
CurrentGamesFarming.Clear();
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -31,12 +31,12 @@ namespace ArchiSteamFarm {
|
||||
internal sealed class ConcurrentSortedHashSet<T> : IDisposable, IReadOnlyCollection<T>, ISet<T> {
|
||||
public int Count {
|
||||
get {
|
||||
SemaphoreSlim.Wait();
|
||||
CollectionSemaphore.Wait();
|
||||
|
||||
try {
|
||||
return BackingCollection.Count;
|
||||
} finally {
|
||||
SemaphoreSlim.Release();
|
||||
CollectionSemaphore.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,159 +44,159 @@ namespace ArchiSteamFarm {
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
private readonly HashSet<T> BackingCollection = new HashSet<T>();
|
||||
private readonly SemaphoreSlim SemaphoreSlim = new SemaphoreSlim(1);
|
||||
private readonly SemaphoreSlim CollectionSemaphore = new SemaphoreSlim(1, 1);
|
||||
|
||||
public bool Add(T item) {
|
||||
SemaphoreSlim.Wait();
|
||||
CollectionSemaphore.Wait();
|
||||
|
||||
try {
|
||||
return BackingCollection.Add(item);
|
||||
} finally {
|
||||
SemaphoreSlim.Release();
|
||||
CollectionSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear() {
|
||||
SemaphoreSlim.Wait();
|
||||
CollectionSemaphore.Wait();
|
||||
|
||||
try {
|
||||
BackingCollection.Clear();
|
||||
} finally {
|
||||
SemaphoreSlim.Release();
|
||||
CollectionSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public bool Contains(T item) {
|
||||
SemaphoreSlim.Wait();
|
||||
CollectionSemaphore.Wait();
|
||||
|
||||
try {
|
||||
return BackingCollection.Contains(item);
|
||||
} finally {
|
||||
SemaphoreSlim.Release();
|
||||
CollectionSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public void CopyTo(T[] array, int arrayIndex) {
|
||||
SemaphoreSlim.Wait();
|
||||
CollectionSemaphore.Wait();
|
||||
|
||||
try {
|
||||
BackingCollection.CopyTo(array, arrayIndex);
|
||||
} finally {
|
||||
SemaphoreSlim.Release();
|
||||
CollectionSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() => SemaphoreSlim.Dispose();
|
||||
public void Dispose() => CollectionSemaphore.Dispose();
|
||||
|
||||
public void ExceptWith(IEnumerable<T> other) {
|
||||
SemaphoreSlim.Wait();
|
||||
CollectionSemaphore.Wait();
|
||||
|
||||
try {
|
||||
BackingCollection.ExceptWith(other);
|
||||
} finally {
|
||||
SemaphoreSlim.Release();
|
||||
CollectionSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<T> GetEnumerator() => new ConcurrentEnumerator<T>(BackingCollection, SemaphoreSlim);
|
||||
public IEnumerator<T> GetEnumerator() => new ConcurrentEnumerator<T>(BackingCollection, CollectionSemaphore);
|
||||
|
||||
public void IntersectWith(IEnumerable<T> other) {
|
||||
SemaphoreSlim.Wait();
|
||||
CollectionSemaphore.Wait();
|
||||
|
||||
try {
|
||||
BackingCollection.IntersectWith(other);
|
||||
} finally {
|
||||
SemaphoreSlim.Release();
|
||||
CollectionSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsProperSubsetOf(IEnumerable<T> other) {
|
||||
SemaphoreSlim.Wait();
|
||||
CollectionSemaphore.Wait();
|
||||
|
||||
try {
|
||||
return BackingCollection.IsProperSubsetOf(other);
|
||||
} finally {
|
||||
SemaphoreSlim.Release();
|
||||
CollectionSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsProperSupersetOf(IEnumerable<T> other) {
|
||||
SemaphoreSlim.Wait();
|
||||
CollectionSemaphore.Wait();
|
||||
|
||||
try {
|
||||
return BackingCollection.IsProperSupersetOf(other);
|
||||
} finally {
|
||||
SemaphoreSlim.Release();
|
||||
CollectionSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsSubsetOf(IEnumerable<T> other) {
|
||||
SemaphoreSlim.Wait();
|
||||
CollectionSemaphore.Wait();
|
||||
|
||||
try {
|
||||
return BackingCollection.IsSubsetOf(other);
|
||||
} finally {
|
||||
SemaphoreSlim.Release();
|
||||
CollectionSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsSupersetOf(IEnumerable<T> other) {
|
||||
SemaphoreSlim.Wait();
|
||||
CollectionSemaphore.Wait();
|
||||
|
||||
try {
|
||||
return BackingCollection.IsSupersetOf(other);
|
||||
} finally {
|
||||
SemaphoreSlim.Release();
|
||||
CollectionSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public bool Overlaps(IEnumerable<T> other) {
|
||||
SemaphoreSlim.Wait();
|
||||
CollectionSemaphore.Wait();
|
||||
|
||||
try {
|
||||
return BackingCollection.Overlaps(other);
|
||||
} finally {
|
||||
SemaphoreSlim.Release();
|
||||
CollectionSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public bool Remove(T item) {
|
||||
SemaphoreSlim.Wait();
|
||||
CollectionSemaphore.Wait();
|
||||
|
||||
try {
|
||||
return BackingCollection.Remove(item);
|
||||
} finally {
|
||||
SemaphoreSlim.Release();
|
||||
CollectionSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public bool SetEquals(IEnumerable<T> other) {
|
||||
SemaphoreSlim.Wait();
|
||||
CollectionSemaphore.Wait();
|
||||
|
||||
try {
|
||||
return BackingCollection.SetEquals(other);
|
||||
} finally {
|
||||
SemaphoreSlim.Release();
|
||||
CollectionSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public void SymmetricExceptWith(IEnumerable<T> other) {
|
||||
SemaphoreSlim.Wait();
|
||||
CollectionSemaphore.Wait();
|
||||
|
||||
try {
|
||||
BackingCollection.SymmetricExceptWith(other);
|
||||
} finally {
|
||||
SemaphoreSlim.Release();
|
||||
CollectionSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public void UnionWith(IEnumerable<T> other) {
|
||||
SemaphoreSlim.Wait();
|
||||
CollectionSemaphore.Wait();
|
||||
|
||||
try {
|
||||
BackingCollection.UnionWith(other);
|
||||
} finally {
|
||||
SemaphoreSlim.Release();
|
||||
CollectionSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,7 +204,7 @@ namespace ArchiSteamFarm {
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
internal void ReplaceWith(IEnumerable<T> other) {
|
||||
SemaphoreSlim.Wait();
|
||||
CollectionSemaphore.Wait();
|
||||
|
||||
try {
|
||||
BackingCollection.Clear();
|
||||
@@ -213,7 +213,7 @@ namespace ArchiSteamFarm {
|
||||
BackingCollection.Add(item);
|
||||
}
|
||||
} finally {
|
||||
SemaphoreSlim.Release();
|
||||
CollectionSemaphore.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,8 +40,8 @@ namespace ArchiSteamFarm {
|
||||
internal const byte DefaultLoginLimiterDelay = 10;
|
||||
internal const string UlongStringPrefix = "s_";
|
||||
|
||||
// This is hardcoded blacklist which should not be possible to change
|
||||
internal static readonly HashSet<uint> GlobalBlacklist = new HashSet<uint> { 267420, 303700, 335590, 368020, 402590, 425280, 480730, 566020, 639900 };
|
||||
internal static readonly HashSet<uint> GamesBlacklist = new HashSet<uint> { 402590 }; // Games with broken/unobtainable card drops
|
||||
internal static readonly HashSet<uint> SalesBlacklist = new HashSet<uint> { 267420, 303700, 335590, 368020, 425280, 480730, 566020, 639900 }; // Steam Summer/Winter sales
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal readonly bool AutoRestart = true;
|
||||
@@ -49,6 +49,9 @@ namespace ArchiSteamFarm {
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal readonly bool AutoUpdates = true;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal readonly byte BackgroundGCPeriod;
|
||||
|
||||
[SuppressMessage("ReSharper", "CollectionNeverUpdated.Global")]
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal readonly HashSet<uint> Blacklist = new HashSet<uint>();
|
||||
@@ -102,7 +105,7 @@ namespace ArchiSteamFarm {
|
||||
internal readonly bool Statistics = true;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal readonly ProtocolTypes SteamProtocols = ProtocolTypes.All;
|
||||
internal readonly ProtocolTypes SteamProtocols = ProtocolTypes.Tcp;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal readonly EUpdateChannel UpdateChannel = EUpdateChannel.Stable;
|
||||
|
||||
@@ -42,24 +42,11 @@ namespace ArchiSteamFarm {
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal readonly InMemoryServerListProvider ServerListProvider = new InMemoryServerListProvider();
|
||||
|
||||
private readonly object FileLock = new object();
|
||||
private readonly SemaphoreSlim FileSemaphore = new SemaphoreSlim(1, 1);
|
||||
private readonly SemaphoreSlim PackagesRefreshSemaphore = new SemaphoreSlim(1, 1);
|
||||
|
||||
private readonly SemaphoreSlim PackagesRefreshSemaphore = new SemaphoreSlim(1);
|
||||
|
||||
internal uint CellID {
|
||||
get => _CellID;
|
||||
set {
|
||||
if ((value == 0) || (_CellID == value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
_CellID = value;
|
||||
Save();
|
||||
}
|
||||
}
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
private uint _CellID;
|
||||
[JsonProperty(PropertyName = "_CellID", Required = Required.DisallowNull)]
|
||||
internal uint CellID { get; private set; }
|
||||
|
||||
private string FilePath;
|
||||
|
||||
@@ -70,13 +57,20 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
FilePath = filePath;
|
||||
Save();
|
||||
Save().Wait();
|
||||
}
|
||||
|
||||
// This constructor is used only by deserializer
|
||||
private GlobalDatabase() => ServerListProvider.ServerListUpdated += OnServerListUpdated;
|
||||
|
||||
public void Dispose() => ServerListProvider.ServerListUpdated -= OnServerListUpdated;
|
||||
public void Dispose() {
|
||||
// Events we registered
|
||||
ServerListProvider.ServerListUpdated -= OnServerListUpdated;
|
||||
|
||||
// Those are objects that are always being created if constructor doesn't throw exception
|
||||
FileSemaphore.Dispose();
|
||||
PackagesRefreshSemaphore.Dispose();
|
||||
}
|
||||
|
||||
internal static GlobalDatabase Load(string filePath) {
|
||||
if (string.IsNullOrEmpty(filePath)) {
|
||||
@@ -136,35 +130,46 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
Save();
|
||||
await Save().ConfigureAwait(false);
|
||||
} finally {
|
||||
PackagesRefreshSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnServerListUpdated(object sender, EventArgs e) => Save();
|
||||
internal async Task SetCellID(uint value = 0) {
|
||||
if (value == CellID) {
|
||||
return;
|
||||
}
|
||||
|
||||
private void Save() {
|
||||
CellID = value;
|
||||
await Save().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async void OnServerListUpdated(object sender, EventArgs e) => await Save().ConfigureAwait(false);
|
||||
|
||||
private async Task Save() {
|
||||
string json = JsonConvert.SerializeObject(this);
|
||||
if (string.IsNullOrEmpty(json)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(json));
|
||||
return;
|
||||
}
|
||||
|
||||
lock (FileLock) {
|
||||
string newFilePath = FilePath + ".new";
|
||||
string newFilePath = FilePath + ".new";
|
||||
|
||||
try {
|
||||
File.WriteAllText(newFilePath, json);
|
||||
await FileSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
if (File.Exists(FilePath)) {
|
||||
File.Replace(newFilePath, FilePath, null);
|
||||
} else {
|
||||
File.Move(newFilePath, FilePath);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
ASF.ArchiLogger.LogGenericException(e);
|
||||
try {
|
||||
await File.WriteAllTextAsync(newFilePath, json).ConfigureAwait(false);
|
||||
|
||||
if (File.Exists(FilePath)) {
|
||||
File.Replace(newFilePath, FilePath, null);
|
||||
} else {
|
||||
File.Move(newFilePath, FilePath);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
ASF.ArchiLogger.LogGenericException(e);
|
||||
} finally {
|
||||
FileSemaphore.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,18 +28,21 @@ using System.Threading;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal static class Hacks {
|
||||
private const byte GarbageCollectorDelay = 1;
|
||||
|
||||
private static Timer GarbageCollectionTimer;
|
||||
private static Timer GarbageCompactionTimer;
|
||||
|
||||
internal static void Init() {
|
||||
internal static void EnableBackgroundGC(byte period) {
|
||||
if (period == 0) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(period));
|
||||
return;
|
||||
}
|
||||
|
||||
if (GarbageCollectionTimer == null) {
|
||||
GarbageCollectionTimer = new Timer(
|
||||
e => GC.Collect(),
|
||||
null,
|
||||
TimeSpan.FromSeconds(GarbageCollectorDelay), // Delay
|
||||
TimeSpan.FromSeconds(GarbageCollectorDelay) // Period
|
||||
TimeSpan.FromSeconds(period), // Delay
|
||||
TimeSpan.FromSeconds(period) // Period
|
||||
);
|
||||
}
|
||||
|
||||
@@ -47,8 +50,8 @@ namespace ArchiSteamFarm {
|
||||
GarbageCompactionTimer = new Timer(
|
||||
e => GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce,
|
||||
null,
|
||||
TimeSpan.FromMinutes(GarbageCollectorDelay), // Delay
|
||||
TimeSpan.FromMinutes(GarbageCollectorDelay) // Period
|
||||
TimeSpan.FromMinutes(period), // Delay
|
||||
TimeSpan.FromMinutes(period) // Period
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,11 +168,11 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
Encoding encoding = Encoding.UTF8;
|
||||
|
||||
response.ContentEncoding = encoding;
|
||||
response.ContentType = "text/plain; charset=" + encoding.WebName;
|
||||
|
||||
string html = "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"></head><body><p>" + message + "</p></body></html>";
|
||||
|
||||
byte[] buffer = encoding.GetBytes(html);
|
||||
byte[] buffer = encoding.GetBytes(message + Environment.NewLine);
|
||||
response.ContentLength64 = buffer.Length;
|
||||
|
||||
await response.OutputStream.WriteAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
|
||||
|
||||
@@ -130,8 +130,7 @@
|
||||
<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>ErrorPropertiConfiginvalid
|
||||
{0} akan diubah dengan nama properti konfigurasi, {1} akan diubah dengan nilai invalid</value>
|
||||
<value>Konfigurasi properti {0} tidak valid: {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">
|
||||
@@ -182,14 +181,19 @@
|
||||
<data name="ErrorUpdateCheckFailed" xml:space="preserve">
|
||||
<value>Tidak dapat memeriksa versi terbaru!</value>
|
||||
</data>
|
||||
|
||||
<data name="ErrorUpdateNoAssetForThisVersion" xml:space="preserve">
|
||||
<value>Tidak bisa melanjutkan dengan pembaruan karena tidak ada asset yang berkaitan dengan versi yang berjalan sekarang! Pembaruan otomatis ke versi tersebut tidak mungkin.</value>
|
||||
</data>
|
||||
<data name="ErrorUpdateNoAssets" xml:space="preserve">
|
||||
<value>Tidak bisa melanjutkan update karena tak ada aset yang termasuk dalam versi tersebut!</value>
|
||||
</data>
|
||||
<data name="ErrorUserInputRunningInHeadlessMode" xml:space="preserve">
|
||||
<value>Menerima permintaan untuk input pengguna, tetapi proses berjalan dalam mode Headless!</value>
|
||||
</data>
|
||||
|
||||
<data name="ErrorIPCAccessDenied" xml:space="preserve">
|
||||
<value>Menolak untuk menangani permintaan karena SteamOwnerID tidak diatur!</value>
|
||||
<comment>SteamOwnerID is name of bot config property, it should not be translated</comment>
|
||||
</data>
|
||||
<data name="Exiting" xml:space="preserve">
|
||||
<value>Menutup...</value>
|
||||
</data>
|
||||
@@ -239,9 +243,12 @@
|
||||
<data name="UpdateCheckingNewVersion" xml:space="preserve">
|
||||
<value>Sedang mengecek versi terbaru...</value>
|
||||
</data>
|
||||
|
||||
<data name="UpdateDownloadingNewVersion" xml:space="preserve">
|
||||
<value>Mengunduh versi baru: {0} ({1} MB)... Sambil menunggu, pertimbangkan untuk mengapresiasi seluruh kerja keras dengan mendonasi! :)</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>Proses update selesai!</value>
|
||||
<value>Proses pembaruan selesai!</value>
|
||||
</data>
|
||||
<data name="UpdateNewVersionAvailable" xml:space="preserve">
|
||||
<value>Versi terbaru ASF tersedia! Pertimbangkan untuk di-update!</value>
|
||||
@@ -278,19 +285,32 @@
|
||||
<value>Masukkan suatu nilai yang tidak terdokumentasi {0}: </value>
|
||||
<comment>{0} will be replaced by property name. Please note that this translation should end with space</comment>
|
||||
</data>
|
||||
|
||||
<data name="UserInputIPCHost" xml:space="preserve">
|
||||
<value>Masukkan host WCF Anda: </value>
|
||||
<comment>Please note that this translation should end with space</comment>
|
||||
</data>
|
||||
<data name="WarningUnknownValuePleaseReport" xml:space="preserve">
|
||||
<value>Menerima nilai yang tidak diketahui untuk {0}, laporkan hal ini: {1}</value>
|
||||
<value>Menerima nilai yang tidak diketahui untuk {0}, mohon laporkan ini: {1}</value>
|
||||
<comment>{0} will be replaced by object's name, {1} will be replaced by value for that object</comment>
|
||||
</data>
|
||||
<data name="WarningTooManyGamesToPlay" xml:space="preserve">
|
||||
<value>Tidak dapat bermain lebih dari {0} game secara bersamaan, hanya {0} entri pertama dari {1} game yang akan digunakan</value>
|
||||
<comment>{0} will be replaced by max number of games, {1} will be replaced by name of the configuration property</comment>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="ErrorIPCAddressAccessDeniedException" xml:space="preserve">
|
||||
<value>Layanan WCF tidak bisa dimulai karena AddressAccessDeniedException! Jika anda ingin menggunakan layanan WCF yang disediakan ASF, jalankan ASF sebagai Administrator, atau berikan izin yang benar!</value>
|
||||
</data>
|
||||
<data name="IPCAnswered" xml:space="preserve">
|
||||
<value>Jawaban untuk perintah IPC: {0} dengan: {1}</value>
|
||||
<comment>{0} will be replaced by IPC command, {1} will be replaced by IPC answer</comment>
|
||||
</data>
|
||||
<data name="IPCReady" xml:space="preserve">
|
||||
<value>Server IPC siap!</value>
|
||||
</data>
|
||||
<data name="IPCStarting" xml:space="preserve">
|
||||
<value>Memulai server IPC di {0}...</value>
|
||||
<comment>{0} will be replaced by IPC hostname</comment>
|
||||
</data>
|
||||
<data name="BotAlreadyStopped" xml:space="preserve">
|
||||
<value>Bot ini sudah berhenti!</value>
|
||||
</data>
|
||||
@@ -317,14 +337,14 @@
|
||||
<value>Mengecek halaman badge lainnya...</value>
|
||||
</data>
|
||||
<data name="ChosenFarmingAlgorithm" xml:space="preserve">
|
||||
<value>Memilih Algoritma Idling: {0}</value>
|
||||
<value>Memilih algoritma idling: {0}</value>
|
||||
<comment>{0} will be replaced by the name of chosen idling algorithm</comment>
|
||||
</data>
|
||||
<data name="Done" xml:space="preserve">
|
||||
<value>Selesai!</value>
|
||||
</data>
|
||||
<data name="GamesToIdle" xml:space="preserve">
|
||||
<value>Kami memiliki total {0} permainan ({1} kartu) meninggalkan ke siaga (~{2} yang tersisa)...</value>
|
||||
<value>Kita memiliki total {0} permainan ({1} kartu) tersisa untuk idle (~{2} tersisa)...</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">
|
||||
@@ -360,7 +380,7 @@
|
||||
<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>Bermain sedang tidak tersedia, kami akan mencoba lagi nanti!</value>
|
||||
<value>Bermain sedang tidak tersedia, kita akan coba lagi nanti!</value>
|
||||
</data>
|
||||
<data name="StillIdling" xml:space="preserve">
|
||||
<value>Masih idling: {0} ({1})</value>
|
||||
@@ -460,7 +480,7 @@
|
||||
<comment>{0} will be replaced by logging off reason (string)</comment>
|
||||
</data>
|
||||
<data name="BotLoggedOn" xml:space="preserve">
|
||||
<value>Berhasil Login!</value>
|
||||
<value>Berhasil login!</value>
|
||||
</data>
|
||||
<data name="BotLoggingIn" xml:space="preserve">
|
||||
<value>Sedang masuk...</value>
|
||||
@@ -650,5 +670,8 @@
|
||||
<value>Selesai membersihkan antrian penemuan Steam #{0}.</value>
|
||||
<comment>{0} will be replaced by queue number</comment>
|
||||
</data>
|
||||
|
||||
<data name="BotOwnsOverview" xml:space="preserve">
|
||||
<value>Ada {0}/{1} bot yang sudah memiliki semua permainan yang sedang diperiksa.</value>
|
||||
<comment>{0} will be replaced by number of bots that already own games being checked, {1} will be replaced by total number of bots that were checked during the process</comment>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -218,7 +218,7 @@
|
||||
<value>无账号正在运行,即将退出...</value>
|
||||
</data>
|
||||
<data name="RefreshingOurSession" xml:space="preserve">
|
||||
<value>刷新会话 !</value>
|
||||
<value>正在刷新会话!</value>
|
||||
</data>
|
||||
<data name="RejectingTrade" xml:space="preserve">
|
||||
<value>拒绝交易︰ {0}</value>
|
||||
@@ -341,10 +341,10 @@
|
||||
<comment>{0} will be replaced by the name of chosen idling algorithm</comment>
|
||||
</data>
|
||||
<data name="Done" xml:space="preserve">
|
||||
<value>完成。</value>
|
||||
<value>完成!</value>
|
||||
</data>
|
||||
<data name="GamesToIdle" xml:space="preserve">
|
||||
<value>共有{0} 个游戏(共计{1} 张卡) 等待挂卡(~还剩{2})...</value>
|
||||
<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">
|
||||
@@ -363,13 +363,13 @@
|
||||
<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>
|
||||
<value>已停止挂卡!</value>
|
||||
</data>
|
||||
<data name="IgnoredStickyPauseEnabled" xml:space="preserve">
|
||||
<value>请求已忽略,因为强制暂停已开启</value>
|
||||
</data>
|
||||
<data name="NothingToIdle" xml:space="preserve">
|
||||
<value>该账户已经无卡可挂</value>
|
||||
<value>该账户已经无卡可挂!</value>
|
||||
</data>
|
||||
<data name="NowIdling" xml:space="preserve">
|
||||
<value>正在挂卡︰{0} ({1})</value>
|
||||
|
||||
@@ -42,14 +42,14 @@ namespace ArchiSteamFarm {
|
||||
private const byte CodeInterval = 30;
|
||||
|
||||
private static readonly char[] CodeCharacters = { '2', '3', '4', '5', '6', '7', '8', '9', 'B', 'C', 'D', 'F', 'G', 'H', 'J', 'K', 'M', 'N', 'P', 'Q', 'R', 'T', 'V', 'W', 'X', 'Y' };
|
||||
private static readonly SemaphoreSlim TimeSemaphore = new SemaphoreSlim(1);
|
||||
private static readonly SemaphoreSlim TimeSemaphore = new SemaphoreSlim(1, 1);
|
||||
|
||||
private static int? SteamTimeDifference;
|
||||
|
||||
// "ERROR" is being used by SteamDesktopAuthenticator
|
||||
internal bool HasCorrectDeviceID => !string.IsNullOrEmpty(DeviceID) && !DeviceID.Equals("ERROR");
|
||||
|
||||
private readonly SemaphoreSlim ConfirmationsSemaphore = new SemaphoreSlim(1);
|
||||
private readonly SemaphoreSlim ConfirmationsSemaphore = new SemaphoreSlim(1, 1);
|
||||
|
||||
#pragma warning disable 649
|
||||
[JsonProperty(PropertyName = "identity_secret", Required = Required.Always)]
|
||||
|
||||
@@ -23,8 +23,10 @@
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using ArchiSteamFarm.Localization;
|
||||
using Mono.Unix;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal static class OS {
|
||||
@@ -44,6 +46,16 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
internal static void UnixSetFileAccessExecutable(string path) {
|
||||
if (!File.Exists(path) || !UnixFileSystemInfo.TryGetFileSystemEntry(path, out UnixFileSystemInfo entry)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!entry.FileAccessPermissions.HasFlag(FileAccessPermissions.UserExecute)) {
|
||||
entry.FileAccessPermissions = entry.FileAccessPermissions | FileAccessPermissions.UserExecute;
|
||||
}
|
||||
}
|
||||
|
||||
private static void DisableQuickEditMode() {
|
||||
// http://stackoverflow.com/questions/30418886/how-and-why-does-quickedit-mode-in-command-prompt-freeze-applications
|
||||
IntPtr consoleHandle = NativeMethods.GetStdHandle(NativeMethods.StandardInputHandle);
|
||||
|
||||
@@ -31,8 +31,6 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Resources;
|
||||
using System.Runtime;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ArchiSteamFarm.Localization;
|
||||
using NLog;
|
||||
@@ -53,7 +51,11 @@ namespace ArchiSteamFarm {
|
||||
internal static WebBrowser WebBrowser { get; private set; }
|
||||
|
||||
private static readonly object ConsoleLock = new object();
|
||||
private static readonly ManualResetEventSlim ShutdownResetEvent = new ManualResetEventSlim(false);
|
||||
|
||||
// We need to keep this one assigned and not calculated on-demand
|
||||
private static readonly string ProcessFileName = Process.GetCurrentProcess().MainModule.FileName;
|
||||
|
||||
private static readonly TaskCompletionSource<bool> ShutdownResetEvent = new TaskCompletionSource<bool>();
|
||||
|
||||
private static bool ShutdownSequenceInitialized;
|
||||
|
||||
@@ -82,33 +84,39 @@ namespace ArchiSteamFarm {
|
||||
switch (userInputType) {
|
||||
case ASF.EUserInputType.DeviceID:
|
||||
Console.Write(Bot.FormatBotResponse(Strings.UserInputDeviceID, botName));
|
||||
result = Console.ReadLine();
|
||||
break;
|
||||
case ASF.EUserInputType.IPCHostname:
|
||||
Console.Write(Bot.FormatBotResponse(Strings.UserInputIPCHost, botName));
|
||||
result = Console.ReadLine();
|
||||
break;
|
||||
case ASF.EUserInputType.Login:
|
||||
Console.Write(Bot.FormatBotResponse(Strings.UserInputSteamLogin, botName));
|
||||
result = Console.ReadLine();
|
||||
break;
|
||||
case ASF.EUserInputType.Password:
|
||||
Console.Write(Bot.FormatBotResponse(Strings.UserInputSteamPassword, botName));
|
||||
result = Utilities.ReadLineMasked();
|
||||
break;
|
||||
case ASF.EUserInputType.SteamGuard:
|
||||
Console.Write(Bot.FormatBotResponse(Strings.UserInputSteamGuard, botName));
|
||||
result = Console.ReadLine();
|
||||
break;
|
||||
case ASF.EUserInputType.SteamParentalPIN:
|
||||
Console.Write(Bot.FormatBotResponse(Strings.UserInputSteamParentalPIN, botName));
|
||||
result = Utilities.ReadLineMasked();
|
||||
break;
|
||||
case ASF.EUserInputType.TwoFactorAuthentication:
|
||||
Console.Write(Bot.FormatBotResponse(Strings.UserInputSteam2FA, botName));
|
||||
result = Console.ReadLine();
|
||||
break;
|
||||
default:
|
||||
ASF.ArchiLogger.LogGenericWarning(string.Format(Strings.WarningUnknownValuePleaseReport, nameof(userInputType), userInputType));
|
||||
Console.Write(Bot.FormatBotResponse(string.Format(Strings.UserInputUnknown, userInputType), botName));
|
||||
result = Console.ReadLine();
|
||||
break;
|
||||
}
|
||||
|
||||
result = Console.ReadLine();
|
||||
|
||||
if (!Console.IsOutputRedirected) {
|
||||
Console.Clear(); // For security purposes
|
||||
}
|
||||
@@ -124,13 +132,11 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
string executable = Process.GetCurrentProcess().MainModule.FileName;
|
||||
string executableName = Path.GetFileNameWithoutExtension(executable);
|
||||
|
||||
string executableName = Path.GetFileNameWithoutExtension(ProcessFileName);
|
||||
IEnumerable<string> arguments = Environment.GetCommandLineArgs().Skip(executableName.Equals(SharedInfo.AssemblyName) ? 1 : 0);
|
||||
|
||||
try {
|
||||
Process.Start(executable, string.Join(" ", arguments));
|
||||
Process.Start(ProcessFileName, string.Join(" ", arguments));
|
||||
} catch (Exception e) {
|
||||
ASF.ArchiLogger.LogGenericException(e);
|
||||
}
|
||||
@@ -138,7 +144,7 @@ namespace ArchiSteamFarm {
|
||||
// Give new process some time to take over the window (if needed)
|
||||
await Task.Delay(2000).ConfigureAwait(false);
|
||||
|
||||
ShutdownResetEvent.Set();
|
||||
ShutdownResetEvent.TrySetResult(true);
|
||||
Environment.Exit(0);
|
||||
}
|
||||
|
||||
@@ -155,7 +161,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
private static async Task InitASF(string[] args) {
|
||||
ASF.ArchiLogger.LogGenericInfo("ASF V" + SharedInfo.Version);
|
||||
ASF.ArchiLogger.LogGenericInfo("ASF V" + SharedInfo.Version + " (" + SharedInfo.ModuleVersion + ")");
|
||||
|
||||
await InitGlobalConfigAndLanguage().ConfigureAwait(false);
|
||||
await InitGlobalDatabaseAndServices().ConfigureAwait(false);
|
||||
@@ -231,8 +237,8 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
if (GCSettings.IsServerGC) {
|
||||
Hacks.Init();
|
||||
if (GlobalConfig.BackgroundGCPeriod > 0) {
|
||||
Hacks.EnableBackgroundGC(GlobalConfig.BackgroundGCPeriod);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(GlobalConfig.CurrentCulture)) {
|
||||
@@ -318,36 +324,35 @@ namespace ArchiSteamFarm {
|
||||
|
||||
ShutdownSequenceInitialized = true;
|
||||
|
||||
if (Bot.Bots.Count == 0) {
|
||||
return true;
|
||||
}
|
||||
if (Bot.Bots.Count > 0) {
|
||||
IEnumerable<Task> tasks = Bot.Bots.Values.Select(bot => Task.Run(() => bot.Stop(false)));
|
||||
|
||||
IEnumerable<Task> tasks = Bot.Bots.Values.Select(bot => Task.Run(() => bot.Stop(false)));
|
||||
switch (GlobalConfig.OptimizationMode) {
|
||||
case GlobalConfig.EOptimizationMode.MinMemoryUsage:
|
||||
foreach (Task task in tasks) {
|
||||
await Task.WhenAny(task, Task.Delay(WebBrowser.MaxTries * 1000)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
switch (GlobalConfig.OptimizationMode) {
|
||||
case GlobalConfig.EOptimizationMode.MinMemoryUsage:
|
||||
foreach (Task task in tasks) {
|
||||
await Task.WhenAny(task, Task.Delay(WebBrowser.MaxTries * 1000)).ConfigureAwait(false);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
await Task.WhenAny(Task.WhenAll(tasks), Task.Delay(Bot.Bots.Count * WebBrowser.MaxTries * 1000)).ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
await Task.WhenAny(Task.WhenAll(tasks), Task.Delay(Bot.Bots.Count * WebBrowser.MaxTries * 1000)).ConfigureAwait(false);
|
||||
break;
|
||||
// Extra second for Steam requests to go through
|
||||
await Task.Delay(1000).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
LogManager.Flush();
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void Main(string[] args) {
|
||||
Init(args).Wait();
|
||||
private static async Task Main(string[] args) {
|
||||
// Initialize
|
||||
await Init(args).ConfigureAwait(false);
|
||||
|
||||
// Wait for signal to shutdown
|
||||
ShutdownResetEvent.Wait();
|
||||
|
||||
// We got a signal to shutdown
|
||||
Exit().Wait();
|
||||
// Wait for shutdown event
|
||||
await ShutdownResetEvent.Task.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static void OnProcessExit(object sender, EventArgs e) => IPC.Stop();
|
||||
@@ -427,7 +432,7 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
ShutdownResetEvent.Set();
|
||||
ShutdownResetEvent.TrySetResult(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,16 +38,8 @@ namespace ArchiSteamFarm {
|
||||
internal readonly ProtocolTypes ProtocolTypes;
|
||||
|
||||
internal ServerRecordEndPoint(string host, ushort port, ProtocolTypes protocolTypes) {
|
||||
if (string.IsNullOrEmpty(host)) {
|
||||
throw new ArgumentNullException(nameof(host));
|
||||
}
|
||||
|
||||
if (port == 0) {
|
||||
throw new ArgumentNullException(nameof(port));
|
||||
}
|
||||
|
||||
if (protocolTypes == 0) {
|
||||
throw new ArgumentNullException(nameof(protocolTypes));
|
||||
if (string.IsNullOrEmpty(host) || (port == 0) || (protocolTypes == 0)) {
|
||||
throw new ArgumentNullException(nameof(host) + " || " + nameof(port) + " || " + nameof(protocolTypes));
|
||||
}
|
||||
|
||||
Host = host;
|
||||
|
||||
@@ -42,6 +42,7 @@ namespace ArchiSteamFarm {
|
||||
internal const string UpdateDirectory = "_old";
|
||||
internal const string VersionFile = AssemblyName + ".version";
|
||||
|
||||
internal static readonly Version Version = Assembly.GetEntryAssembly().GetName().Version;
|
||||
internal static Guid ModuleVersion => Assembly.GetEntryAssembly().ManifestModule.ModuleVersionId;
|
||||
internal static Version Version => Assembly.GetEntryAssembly().GetName().Version;
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,7 @@ namespace ArchiSteamFarm {
|
||||
private const string URL = "https://" + SharedInfo.StatisticsServer;
|
||||
|
||||
private readonly Bot Bot;
|
||||
private readonly SemaphoreSlim Semaphore = new SemaphoreSlim(1);
|
||||
private readonly SemaphoreSlim RequestsSemaphore = new SemaphoreSlim(1, 1);
|
||||
|
||||
private DateTime LastAnnouncementCheck = DateTime.MinValue;
|
||||
private DateTime LastHeartBeat = DateTime.MinValue;
|
||||
@@ -49,7 +49,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
internal Statistics(Bot bot) => Bot = bot ?? throw new ArgumentNullException(nameof(bot));
|
||||
|
||||
public void Dispose() => Semaphore.Dispose();
|
||||
public void Dispose() => RequestsSemaphore.Dispose();
|
||||
|
||||
internal async Task OnHeartBeat() {
|
||||
// Request persona update if needed
|
||||
@@ -62,7 +62,7 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
await Semaphore.WaitAsync().ConfigureAwait(false);
|
||||
await RequestsSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
try {
|
||||
if (!ShouldSendHeartBeats || (DateTime.UtcNow < LastHeartBeat.AddMinutes(MinHeartBeatTTL))) {
|
||||
@@ -80,7 +80,7 @@ namespace ArchiSteamFarm {
|
||||
LastHeartBeat = DateTime.UtcNow;
|
||||
}
|
||||
} finally {
|
||||
Semaphore.Release();
|
||||
RequestsSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
await Semaphore.WaitAsync().ConfigureAwait(false);
|
||||
await RequestsSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
try {
|
||||
if (DateTime.UtcNow < LastAnnouncementCheck.AddHours(MinAnnouncementCheckTTL)) {
|
||||
@@ -154,7 +154,7 @@ namespace ArchiSteamFarm {
|
||||
ShouldSendHeartBeats = true;
|
||||
}
|
||||
} finally {
|
||||
Semaphore.Release();
|
||||
RequestsSemaphore.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +56,8 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
base.Write(logEvent);
|
||||
|
||||
if (SteamID == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
private readonly Bot Bot;
|
||||
private readonly ConcurrentHashSet<ulong> IgnoredTrades = new ConcurrentHashSet<ulong>();
|
||||
private readonly SemaphoreSlim TradesSemaphore = new SemaphoreSlim(1);
|
||||
private readonly SemaphoreSlim TradesSemaphore = new SemaphoreSlim(1, 1);
|
||||
|
||||
private bool ParsingScheduled;
|
||||
|
||||
@@ -301,7 +301,7 @@ namespace ArchiSteamFarm {
|
||||
// If user has a trade hold, we add extra logic
|
||||
if (holdDuration.Value > 0) {
|
||||
// If trade hold duration exceeds our max, or user asks for cards with short lifespan, reject the trade
|
||||
if ((holdDuration.Value > Program.GlobalConfig.MaxTradeHoldDuration) || tradeOffer.ItemsToGive.Any(item => GlobalConfig.GlobalBlacklist.Contains(item.RealAppID))) {
|
||||
if ((holdDuration.Value > Program.GlobalConfig.MaxTradeHoldDuration) || tradeOffer.ItemsToGive.Any(item => GlobalConfig.SalesBlacklist.Contains(item.RealAppID))) {
|
||||
return new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.RejectedPermanently);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Humanizer;
|
||||
|
||||
@@ -85,6 +86,31 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
internal static string ReadLineMasked(char mask = '*') {
|
||||
StringBuilder result = new StringBuilder();
|
||||
|
||||
ConsoleKeyInfo keyInfo;
|
||||
while ((keyInfo = Console.ReadKey(true)).Key != ConsoleKey.Enter) {
|
||||
if (!char.IsControl(keyInfo.KeyChar)) {
|
||||
result.Append(keyInfo.KeyChar);
|
||||
Console.Write(mask);
|
||||
} else if ((keyInfo.Key == ConsoleKey.Backspace) && (result.Length > 0)) {
|
||||
result.Remove(result.Length - 1, 1);
|
||||
|
||||
if (Console.CursorLeft == 0) {
|
||||
Console.SetCursorPosition(Console.BufferWidth - 1, Console.CursorTop - 1);
|
||||
Console.Write(' ');
|
||||
Console.SetCursorPosition(Console.BufferWidth - 1, Console.CursorTop - 1);
|
||||
} else {
|
||||
Console.Write("\b \b");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
return result.ToString();
|
||||
}
|
||||
|
||||
internal static void StartBackgroundAction(Action action, bool longRunning = true) {
|
||||
if (action == null) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(action));
|
||||
|
||||
@@ -34,7 +34,7 @@ using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal sealed class WebBrowser {
|
||||
internal sealed class WebBrowser : IDisposable {
|
||||
internal const byte MaxTries = 5; // Defines maximum number of recommended tries for a single request
|
||||
|
||||
private const byte ExtendedTimeoutMultiplier = 10; // Defines multiplier of timeout for WebBrowsers dealing with huge data (ASF update)
|
||||
@@ -63,6 +63,8 @@ namespace ArchiSteamFarm {
|
||||
HttpClient.DefaultRequestHeaders.UserAgent.ParseAdd(SharedInfo.AssemblyName + "/" + SharedInfo.Version);
|
||||
}
|
||||
|
||||
public void Dispose() => HttpClient.Dispose();
|
||||
|
||||
internal static void Init() {
|
||||
// Set max connection limit from default of 2 to desired value
|
||||
ServicePointManager.DefaultConnectionLimit = MaxConnections;
|
||||
@@ -535,11 +537,19 @@ namespace ArchiSteamFarm {
|
||||
ushort status = (ushort) responseMessage.StatusCode;
|
||||
if ((status >= 300) && (status <= 399) && (maxRedirections > 0)) {
|
||||
redirectUri = responseMessage.Headers.Location;
|
||||
if (!redirectUri.IsAbsoluteUri) {
|
||||
|
||||
if (redirectUri.IsAbsoluteUri) {
|
||||
switch (redirectUri.Scheme) {
|
||||
case "http":
|
||||
case "https":
|
||||
break;
|
||||
default:
|
||||
// Invalid ones such as "steammobile"
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
redirectUri = new Uri(requestUri.GetLeftPart(UriPartial.Authority) + redirectUri);
|
||||
}
|
||||
|
||||
ASF.ArchiLogger.LogGenericDebug("Asked for <" + requestUri + ">, got unsafely redirected to <" + responseMessage.Headers.Location + ">, resolved URI to: <" + redirectUri + ">");
|
||||
} else {
|
||||
if (!Debugging.IsDebugBuild) {
|
||||
return null;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"AutoRestart": true,
|
||||
"AutoUpdates": true,
|
||||
"BackgroundGCPeriod": 0,
|
||||
"Blacklist": [],
|
||||
"ConnectionTimeout": 60,
|
||||
"CurrentCulture": null,
|
||||
@@ -18,6 +19,6 @@
|
||||
"OptimizationMode": 0,
|
||||
"Statistics": true,
|
||||
"SteamOwnerID": 0,
|
||||
"SteamProtocols": 7,
|
||||
"SteamProtocols": 1,
|
||||
"UpdateChannel": 1
|
||||
}
|
||||
Binary file not shown.
Reference in New Issue
Block a user