mirror of
https://github.com/JustArchiNET/ArchiSteamFarm.git
synced 2025-12-26 03:06:48 +00:00
Compare commits
47 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
28fa7ac538 | ||
|
|
3292816967 | ||
|
|
19aa60cccd | ||
|
|
0efb3eea8e | ||
|
|
1a6e554ee9 | ||
|
|
8587667e27 | ||
|
|
0c79b17163 | ||
|
|
0a5ac68df4 | ||
|
|
64eda2b44f | ||
|
|
3e978f44e7 | ||
|
|
370ab4f5d0 | ||
|
|
8e84dd0af6 | ||
|
|
f2875b1b2b | ||
|
|
702a525893 | ||
|
|
c4c11cf61f | ||
|
|
06b1b8deeb | ||
|
|
0beb000d4b | ||
|
|
acc871e609 | ||
|
|
e8545176e7 | ||
|
|
532a47bda5 | ||
|
|
ddc2d956ba | ||
|
|
563f57e3aa | ||
|
|
0a973cefd2 | ||
|
|
2563ade159 | ||
|
|
1763a109c4 | ||
|
|
f57b5a2166 | ||
|
|
7210e6b86c | ||
|
|
6b241d7439 | ||
|
|
833995ca61 | ||
|
|
40531e9554 | ||
|
|
7a5a9c8a51 | ||
|
|
daae97b3d4 | ||
|
|
eeeaacb4db | ||
|
|
5324a9127d | ||
|
|
bbd267d4f0 | ||
|
|
53495e34e5 | ||
|
|
7026d8b0d5 | ||
|
|
a3b57ac50e | ||
|
|
4910692576 | ||
|
|
5147835d48 | ||
|
|
3a8d79d4da | ||
|
|
f1684c11a3 | ||
|
|
f38b6ef6dd | ||
|
|
d895f3e4bb | ||
|
|
e8b7d7d908 | ||
|
|
e7195b0e68 | ||
|
|
2983ede83d |
@@ -15,9 +15,6 @@ ArchiSteamFarm/logs
|
||||
# Ignore standard out folders for publishing
|
||||
**/out
|
||||
|
||||
# Ignore crowdin CLI secret (if exists)
|
||||
tools/ArchiCrowdin/crowdin_identity.yml
|
||||
|
||||
# _ ____ _____ ____ _
|
||||
# / \ / ___|| ___| | _ \ ___ ___| | _____ _ __
|
||||
# / _ \ \___ \| |_ _____| | | |/ _ \ / __| |/ / _ \ '__|
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/config.yml
vendored
2
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -7,7 +7,7 @@ contact_links:
|
||||
url: https://github.com/JustArchiNET/ArchiSteamFarm/wiki/Localization
|
||||
about: Please use our crowdin platform.
|
||||
- name: Support question or technical issue
|
||||
url: https://github.com/JustArchiNET/ArchiSteamFarm/blob/master/SUPPORT.md
|
||||
url: https://github.com/JustArchiNET/ArchiSteamFarm/blob/master/.github/SUPPORT.md
|
||||
about: Please review our support guidelines.
|
||||
- name: Negative feedback, complaints and demands
|
||||
url: https://www.youtube.com/watch?v=dQw4w9WgXcQ
|
||||
|
||||
0
SECURITY.md → .github/SECURITY.md
vendored
0
SECURITY.md → .github/SECURITY.md
vendored
0
SUPPORT.md → .github/SUPPORT.md
vendored
0
SUPPORT.md → .github/SUPPORT.md
vendored
31
.github/lock.yml
vendored
31
.github/lock.yml
vendored
@@ -1,31 +0,0 @@
|
||||
# Configuration for lock-threads - https://github.com/dessant/lock-threads
|
||||
|
||||
# Number of days of inactivity before a closed issue or pull request is locked
|
||||
daysUntilLock: 30
|
||||
|
||||
# Issues and pull requests with these labels will not be locked. Set to `[]` to disable
|
||||
exemptLabels: []
|
||||
|
||||
# Label to add before locking, such as `outdated`. Set to `false` to disable
|
||||
lockLabel: false
|
||||
|
||||
# Comment to post before locking. Set to `false` to disable
|
||||
lockComment: false
|
||||
|
||||
# Assign `resolved` as the reason for locking. Set to `false` to disable
|
||||
setLockReason: true
|
||||
|
||||
# Limit to only `issues` or `pulls`
|
||||
# only: issues
|
||||
|
||||
# Optionally, specify configuration settings just for `issues` or `pulls`
|
||||
# issues:
|
||||
# exemptLabels:
|
||||
# - help-wanted
|
||||
# lockLabel: outdated
|
||||
|
||||
# pulls:
|
||||
# daysUntilLock: 30
|
||||
|
||||
# Repository to extend settings from
|
||||
# _extends: repo
|
||||
16
.github/workflows/lock.yml
vendored
Normal file
16
.github/workflows/lock.yml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
name: ASF-lock-threads
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 1 * * *'
|
||||
|
||||
jobs:
|
||||
lock:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Lock inactive threads
|
||||
uses: dessant/lock-threads@v2
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
issue-lock-inactive-days: 30
|
||||
pr-lock-inactive-days: 30
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -15,9 +15,6 @@ ArchiSteamFarm/logs
|
||||
# Ignore standard out folders for publishing
|
||||
**/out
|
||||
|
||||
# Ignore crowdin CLI secret (if exists)
|
||||
tools/ArchiCrowdin/crowdin_identity.yml
|
||||
|
||||
# _ _
|
||||
# | | (_) _ __ _ _ __ __
|
||||
# | | | || '_ \ | | | |\ \/ /
|
||||
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -1,6 +1,3 @@
|
||||
[submodule "ASF-WebConfigGenerator"]
|
||||
path = ASF-WebConfigGenerator
|
||||
url = https://github.com/JustArchiNET/ASF-WebConfigGenerator.git
|
||||
[submodule "ASF-ui"]
|
||||
path = ASF-ui
|
||||
url = https://github.com/JustArchiNET/ASF-ui.git
|
||||
|
||||
Submodule ASF-WebConfigGenerator deleted from 82ebd9af65
2
ASF-ui
2
ASF-ui
Submodule ASF-ui updated: 01e264f173...74f056221d
@@ -7,7 +7,7 @@
|
||||
<PackageReference Include="ConfigureAwaitChecker.Analyzer" Version="4.0.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="2.1.2" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="2.1.2" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -138,6 +138,28 @@ namespace ArchiSteamFarm.Tests {
|
||||
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SingleGameAbrynosWasWrongNeutralAccept() {
|
||||
HashSet<Steam.Asset> inventory = new HashSet<Steam.Asset> {
|
||||
CreateItem(1),
|
||||
CreateItem(2, 2),
|
||||
CreateItem(3),
|
||||
CreateItem(4),
|
||||
CreateItem(5)
|
||||
};
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SingleGameDonationAccept() {
|
||||
HashSet<Steam.Asset> inventory = new HashSet<Steam.Asset> {
|
||||
|
||||
@@ -286,6 +286,8 @@ namespace ArchiSteamFarm {
|
||||
try {
|
||||
inventory = await Bot.ArchiWebHandler.GetInventoryAsync(Bot.SteamID, appID, contextID).Where(item => item.Tradable && filterFunction(item)).ToHashSetAsync().ConfigureAwait(false);
|
||||
} catch (HttpRequestException e) {
|
||||
Bot.ArchiLogger.LogGenericWarningException(e);
|
||||
|
||||
return (false, string.Format(Strings.WarningFailedWithError, e.Message));
|
||||
} catch (Exception e) {
|
||||
Bot.ArchiLogger.LogGenericException(e);
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<PackageReference Include="CryptSharpStandard" Version="1.0.0" />
|
||||
<PackageReference Include="Humanizer" Version="2.8.26" />
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2020.1.0" />
|
||||
<PackageReference Include="Markdig.Signed" Version="0.20.0" />
|
||||
<PackageReference Include="Markdig.Signed" Version="0.21.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="Nito.AsyncEx.Coordination" Version="5.0.0" />
|
||||
<PackageReference Include="NLog" Version="4.7.3" />
|
||||
@@ -35,7 +35,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.1'">
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.6" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.7" />
|
||||
<PackageReference Include="System.IO.FileSystem.AccessControl" Version="4.7.0" />
|
||||
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="4.7.0" />
|
||||
</ItemGroup>
|
||||
@@ -48,8 +48,8 @@
|
||||
<PackageReference Include="Microsoft.AspNetCore.ResponseCompression" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="2.2.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.6" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Configuration" Version="3.1.6" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.7" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Configuration" Version="3.1.7" />
|
||||
<Reference Include="System.Net.Http">
|
||||
<HintPath>C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\System.Net.Http.dll</HintPath>
|
||||
</Reference>
|
||||
|
||||
@@ -49,6 +49,7 @@ namespace ArchiSteamFarm {
|
||||
private const char DefaultBackgroundKeysRedeemerSeparator = '\t';
|
||||
private const byte LoginCooldownInMinutes = 25; // Captcha disappears after around 20 minutes, so we make it 25
|
||||
private const uint LoginID = 1242; // This must be the same for all ASF bots and all ASF processes
|
||||
private const byte MaxInvalidPasswordFailures = WebBrowser.MaxTries; // Max InvalidPassword failures in a row before we determine that our password is invalid (because Steam wrongly returns those, of course)
|
||||
private const ushort MaxMessageLength = 5000; // This is a limitation enforced by Steam
|
||||
private const byte MaxTwoFactorCodeFailures = WebBrowser.MaxTries; // Max TwoFactorCodeMismatch failures in a row before we determine that our 2FA credentials are invalid (because Steam wrongly returns those, of course)
|
||||
private const byte RedeemCooldownInHours = 1; // 1 hour since first redeem attempt, this is a limitation enforced by Steam
|
||||
@@ -209,6 +210,7 @@ namespace ArchiSteamFarm {
|
||||
private bool FirstTradeSent;
|
||||
private Timer GamesRedeemerInBackgroundTimer;
|
||||
private byte HeartBeatFailures;
|
||||
private byte InvalidPasswordFailures;
|
||||
private EResult LastLogOnResult;
|
||||
private DateTime LastLogonSessionReplaced;
|
||||
private bool LibraryLocked;
|
||||
@@ -2183,18 +2185,19 @@ namespace ArchiSteamFarm {
|
||||
|
||||
switch (lastLogOnResult) {
|
||||
case EResult.AccountDisabled:
|
||||
case EResult.InvalidPassword when string.IsNullOrEmpty(BotDatabase.LoginKey):
|
||||
// Do not attempt to reconnect, those failures are permanent
|
||||
return;
|
||||
case EResult.InvalidPassword:
|
||||
case EResult.InvalidPassword when !string.IsNullOrEmpty(BotDatabase.LoginKey):
|
||||
BotDatabase.LoginKey = null;
|
||||
ArchiLogger.LogGenericInfo(Strings.BotRemovedExpiredLoginKey);
|
||||
|
||||
break;
|
||||
case EResult.InvalidPassword:
|
||||
case EResult.NoConnection:
|
||||
case EResult.ServiceUnavailable:
|
||||
case EResult.Timeout:
|
||||
case EResult.TryAnotherCM:
|
||||
case EResult.TwoFactorCodeMismatch:
|
||||
await Task.Delay(5000).ConfigureAwait(false);
|
||||
|
||||
break;
|
||||
@@ -2483,7 +2486,6 @@ namespace ArchiSteamFarm {
|
||||
|
||||
switch (callback.Result) {
|
||||
case EResult.AccountDisabled:
|
||||
case EResult.InvalidPassword when string.IsNullOrEmpty(BotDatabase.LoginKey):
|
||||
// Those failures are permanent, we should Stop() the bot if any of those happen
|
||||
ArchiLogger.LogGenericWarning(string.Format(Strings.BotUnableToLogin, callback.Result, callback.ExtendedResult));
|
||||
Stop();
|
||||
@@ -2522,7 +2524,7 @@ namespace ArchiSteamFarm {
|
||||
ArchiLogger.LogGenericInfo(string.Format(Strings.BotLoggedOn, SteamID + (!string.IsNullOrEmpty(callback.VanityURL) ? "/" + callback.VanityURL : "")));
|
||||
|
||||
// Old status for these doesn't matter, we'll update them if needed
|
||||
TwoFactorCodeFailures = 0;
|
||||
InvalidPasswordFailures = TwoFactorCodeFailures = 0;
|
||||
LibraryLocked = PlayingBlocked = false;
|
||||
|
||||
if (PlayingWasBlocked && (PlayingWasBlockedTimer == null)) {
|
||||
@@ -2659,17 +2661,25 @@ namespace ArchiSteamFarm {
|
||||
case EResult.TwoFactorCodeMismatch:
|
||||
ArchiLogger.LogGenericWarning(string.Format(Strings.BotUnableToLogin, callback.Result, callback.ExtendedResult));
|
||||
|
||||
if ((callback.Result == EResult.TwoFactorCodeMismatch) && HasMobileAuthenticator) {
|
||||
if (++TwoFactorCodeFailures >= MaxTwoFactorCodeFailures) {
|
||||
switch (callback.Result) {
|
||||
case EResult.InvalidPassword when string.IsNullOrEmpty(BotDatabase.LoginKey) && (++InvalidPasswordFailures >= MaxInvalidPasswordFailures):
|
||||
InvalidPasswordFailures = 0;
|
||||
ArchiLogger.LogGenericError(string.Format(Strings.BotInvalidPasswordDuringLogin, MaxInvalidPasswordFailures));
|
||||
Stop();
|
||||
|
||||
break;
|
||||
case EResult.TwoFactorCodeMismatch when HasMobileAuthenticator && (++TwoFactorCodeFailures >= MaxTwoFactorCodeFailures):
|
||||
TwoFactorCodeFailures = 0;
|
||||
ArchiLogger.LogGenericError(string.Format(Strings.BotInvalidAuthenticatorDuringLogin, MaxTwoFactorCodeFailures));
|
||||
Stop();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
// Unexpected result, shutdown immediately
|
||||
ArchiLogger.LogGenericError(string.Format(Strings.WarningUnknownValuePleaseReport, nameof(callback.Result), callback.Result));
|
||||
ArchiLogger.LogGenericError(string.Format(Strings.BotUnableToLogin, callback.Result, callback.ExtendedResult));
|
||||
Stop();
|
||||
|
||||
|
||||
@@ -62,6 +62,7 @@ namespace ArchiSteamFarm {
|
||||
private const string DefaultSteamTradeToken = null;
|
||||
private const ETradingPreferences DefaultTradingPreferences = ETradingPreferences.None;
|
||||
private const bool DefaultUseLoginKeys = true;
|
||||
private const byte SteamTradeTokenLength = 8;
|
||||
|
||||
private static readonly ImmutableList<EFarmingOrder> DefaultFarmingOrders = ImmutableList<EFarmingOrder>.Empty;
|
||||
private static readonly ImmutableHashSet<uint> DefaultGamesPlayedWhileIdle = ImmutableHashSet<uint>.Empty;
|
||||
@@ -288,6 +289,10 @@ namespace ArchiSteamFarm {
|
||||
return (false, string.Format(Strings.ErrorConfigPropertyInvalid, nameof(SteamParentalCode), SteamParentalCode));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(SteamTradeToken) && (SteamTradeToken.Length != SteamTradeTokenLength)) {
|
||||
return (false, string.Format(Strings.ErrorConfigPropertyInvalid, nameof(SteamTradeToken), SteamTradeToken));
|
||||
}
|
||||
|
||||
foreach ((ulong steamID, EPermission permission) in SteamUserPermissions) {
|
||||
if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) {
|
||||
return (false, string.Format(Strings.ErrorConfigPropertyInvalid, nameof(SteamUserPermissions), steamID));
|
||||
|
||||
@@ -2864,7 +2864,9 @@ namespace ArchiSteamFarm {
|
||||
completeSuccess = false;
|
||||
}
|
||||
}
|
||||
} catch (HttpRequestException) {
|
||||
} catch (HttpRequestException e) {
|
||||
Bot.ArchiLogger.LogGenericWarningException(e);
|
||||
|
||||
completeSuccess = false;
|
||||
} catch (Exception e) {
|
||||
Bot.ArchiLogger.LogGenericException(e);
|
||||
|
||||
@@ -446,17 +446,62 @@ namespace ArchiSteamFarm.IPC.Controllers.Api {
|
||||
/// Accepts 2FA confirmations of given bots, requires ASF 2FA module to be active on them.
|
||||
/// </summary>
|
||||
[HttpPost("{botNames:required}/TwoFactorAuthentication/Confirmations/Accept")]
|
||||
[Obsolete]
|
||||
[ProducesResponseType(typeof(GenericResponse<IReadOnlyDictionary<string, GenericResponse>>), (int) HttpStatusCode.OK)]
|
||||
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.BadRequest)]
|
||||
public async Task<ActionResult<GenericResponse>> TwoFactorAuthenticationConfirmationsAcceptPost(string botNames) => await TwoFactorAuthenticationConfirmationsPost(botNames, true).ConfigureAwait(false);
|
||||
public async Task<ActionResult<GenericResponse>> TwoFactorAuthenticationConfirmationsAcceptPost(string botNames) {
|
||||
ASF.ArchiLogger.LogGenericWarning(string.Format(Strings.WarningDeprecated, nameof(TwoFactorAuthenticationConfirmationsAcceptPost), nameof(TwoFactorAuthenticationConfirmationsPost)));
|
||||
|
||||
return await TwoFactorAuthenticationConfirmationsPost(botNames, new TwoFactorAuthenticationConfirmationsRequest(true)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Denies 2FA confirmations of given bots, requires ASF 2FA module to be active on them.
|
||||
/// </summary>
|
||||
[HttpPost("{botNames:required}/TwoFactorAuthentication/Confirmations/Cancel")]
|
||||
[Obsolete]
|
||||
[ProducesResponseType(typeof(GenericResponse<IReadOnlyDictionary<string, GenericResponse>>), (int) HttpStatusCode.OK)]
|
||||
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.BadRequest)]
|
||||
public async Task<ActionResult<GenericResponse>> TwoFactorAuthenticationConfirmationsCancelPost(string botNames) => await TwoFactorAuthenticationConfirmationsPost(botNames, false).ConfigureAwait(false);
|
||||
public async Task<ActionResult<GenericResponse>> TwoFactorAuthenticationConfirmationsCancelPost(string botNames) {
|
||||
ASF.ArchiLogger.LogGenericWarning(string.Format(Strings.WarningDeprecated, nameof(TwoFactorAuthenticationConfirmationsCancelPost), nameof(TwoFactorAuthenticationConfirmationsPost)));
|
||||
|
||||
return await TwoFactorAuthenticationConfirmationsPost(botNames, new TwoFactorAuthenticationConfirmationsRequest(false)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles 2FA confirmations of given bots, requires ASF 2FA module to be active on them.
|
||||
/// </summary>
|
||||
[HttpPost("{botNames:required}/TwoFactorAuthentication/Confirmations")]
|
||||
[ProducesResponseType(typeof(GenericResponse<IReadOnlyDictionary<string, GenericResponse>>), (int) HttpStatusCode.OK)]
|
||||
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.BadRequest)]
|
||||
public async Task<ActionResult<GenericResponse>> TwoFactorAuthenticationConfirmationsPost(string botNames, [FromBody] TwoFactorAuthenticationConfirmationsRequest request) {
|
||||
if (string.IsNullOrEmpty(botNames) || (request == null)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(botNames));
|
||||
|
||||
return BadRequest(new GenericResponse<IReadOnlyDictionary<string, GenericResponse>>(false, string.Format(Strings.ErrorIsEmpty, nameof(botNames) + " || " + nameof(request))));
|
||||
}
|
||||
|
||||
if (request.AcceptedType.HasValue && ((request.AcceptedType.Value == MobileAuthenticator.Confirmation.EType.Unknown) || !Enum.IsDefined(typeof(MobileAuthenticator.Confirmation.EType), request.AcceptedType.Value))) {
|
||||
return BadRequest(new GenericResponse(false, string.Format(Strings.ErrorIsInvalid, nameof(request.AcceptedType))));
|
||||
}
|
||||
|
||||
HashSet<Bot> bots = Bot.GetBots(botNames);
|
||||
|
||||
if ((bots == null) || (bots.Count == 0)) {
|
||||
return BadRequest(new GenericResponse<IReadOnlyDictionary<string, GenericResponse>>(false, string.Format(Strings.BotNotFound, botNames)));
|
||||
}
|
||||
|
||||
IList<(bool Success, string Message)> results = await Utilities.InParallel(bots.Select(bot => bot.Actions.HandleTwoFactorAuthenticationConfirmations(request.Accept, request.AcceptedType, request.AcceptedCreatorIDs?.Count > 0 ? request.AcceptedCreatorIDs : null, request.WaitIfNeeded))).ConfigureAwait(false);
|
||||
|
||||
Dictionary<string, GenericResponse> result = new Dictionary<string, GenericResponse>(bots.Count, Bot.BotsComparer);
|
||||
|
||||
foreach (Bot bot in bots) {
|
||||
(bool success, string message) = results[result.Count];
|
||||
result[bot.BotName] = new GenericResponse(success, message);
|
||||
}
|
||||
|
||||
return Ok(new GenericResponse<IReadOnlyDictionary<string, GenericResponse>>(result));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetches 2FA tokens of given bots, requires ASF 2FA module to be active on them.
|
||||
@@ -488,30 +533,5 @@ namespace ArchiSteamFarm.IPC.Controllers.Api {
|
||||
|
||||
return Ok(new GenericResponse<IReadOnlyDictionary<string, GenericResponse<string>>>(result));
|
||||
}
|
||||
|
||||
private async Task<ActionResult<GenericResponse>> TwoFactorAuthenticationConfirmationsPost(string botNames, bool accept) {
|
||||
if (string.IsNullOrEmpty(botNames)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(botNames));
|
||||
|
||||
return BadRequest(new GenericResponse<IReadOnlyDictionary<string, GenericResponse>>(false, string.Format(Strings.ErrorIsEmpty, nameof(botNames))));
|
||||
}
|
||||
|
||||
HashSet<Bot> bots = Bot.GetBots(botNames);
|
||||
|
||||
if ((bots == null) || (bots.Count == 0)) {
|
||||
return BadRequest(new GenericResponse<IReadOnlyDictionary<string, GenericResponse>>(false, string.Format(Strings.BotNotFound, botNames)));
|
||||
}
|
||||
|
||||
IList<(bool Success, string Message)> results = await Utilities.InParallel(bots.Select(bot => bot.Actions.HandleTwoFactorAuthenticationConfirmations(accept))).ConfigureAwait(false);
|
||||
|
||||
Dictionary<string, GenericResponse> result = new Dictionary<string, GenericResponse>(bots.Count, Bot.BotsComparer);
|
||||
|
||||
foreach (Bot bot in bots) {
|
||||
(bool success, string message) = results[result.Count];
|
||||
result[bot.BotName] = new GenericResponse(success, message);
|
||||
}
|
||||
|
||||
return Ok(new GenericResponse<IReadOnlyDictionary<string, GenericResponse>>(result));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2020 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// |
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
// |
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using ArchiSteamFarm.Localization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ArchiSteamFarm.IPC.Requests {
|
||||
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
|
||||
public sealed class TwoFactorAuthenticationConfirmationsRequest {
|
||||
/// <summary>
|
||||
/// Specifies the target action, whether we should accept the confirmations (true), or decline them (false).
|
||||
/// </summary>
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
public readonly bool Accept;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the type of confirmations to handle. If not provided, all confirmation types are considered for an action.
|
||||
/// </summary>
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public readonly MobileAuthenticator.Confirmation.EType? AcceptedType;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies whether we should wait for the confirmations to arrive, in case they're not available immediately. This option makes sense only if <see cref="AcceptedCreatorIDs" /> is specified as well, and in this case ASF will add a few more tries if needed to ensure that all specified IDs are handled. Useful if confirmations are generated with a delay on Steam network side, which happens fairly often.
|
||||
/// </summary>
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public readonly bool WaitIfNeeded;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies IDs of the confirmations that we're supposed to handle. CreatorID of the confirmation is equal to ID of the object that triggered it - e.g. ID of the trade offer, or ID of the market listing. If not provided, or empty array, all confirmation IDs are considered for an action.
|
||||
/// </summary>
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public ImmutableHashSet<ulong> AcceptedCreatorIDs { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// A helper property which works the same as <see cref="AcceptedCreatorIDs" /> but with values written as strings - for javascript compatibility purposes. Use either this one, or <see cref="AcceptedCreatorIDs" />, not both.
|
||||
/// </summary>
|
||||
[JsonProperty(PropertyName = SharedInfo.UlongCompatibilityStringPrefix + nameof(AcceptedCreatorIDs), Required = Required.DisallowNull)]
|
||||
public ImmutableHashSet<string> SAcceptedCreatorIDs {
|
||||
set {
|
||||
if (value == null) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(value));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
HashSet<ulong> acceptedCreatorIDs = new HashSet<ulong>(value.Count);
|
||||
|
||||
foreach (string creatorIDText in value) {
|
||||
if (!ulong.TryParse(creatorIDText, out ulong creatorID) || (creatorID == 0)) {
|
||||
ASF.ArchiLogger.LogGenericError(string.Format(Strings.ErrorIsInvalid, nameof(SAcceptedCreatorIDs)));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
acceptedCreatorIDs.Add(creatorID);
|
||||
}
|
||||
|
||||
AcceptedCreatorIDs = acceptedCreatorIDs.ToImmutableHashSet();
|
||||
}
|
||||
}
|
||||
|
||||
[Obsolete]
|
||||
internal TwoFactorAuthenticationConfirmationsRequest(bool accept) => Accept = accept;
|
||||
|
||||
[JsonConstructor]
|
||||
private TwoFactorAuthenticationConfirmationsRequest() { }
|
||||
}
|
||||
}
|
||||
@@ -170,6 +170,7 @@ namespace ArchiSteamFarm.IPC {
|
||||
}
|
||||
);
|
||||
|
||||
options.CustomSchemaIds(type => type.GetUnifiedName());
|
||||
options.EnableAnnotations(true);
|
||||
options.SchemaFilter<EnumSchemaFilter>();
|
||||
|
||||
|
||||
18
ArchiSteamFarm/Localization/Strings.Designer.cs
generated
18
ArchiSteamFarm/Localization/Strings.Designer.cs
generated
@@ -375,6 +375,15 @@ namespace ArchiSteamFarm.Localization {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wyszukuje zlokalizowany ciąg podobny do ciągu Received InvalidPassword error code {0} times in a row. Your password for this account is most likely wrong, aborting!.
|
||||
/// </summary>
|
||||
public static string BotInvalidPasswordDuringLogin {
|
||||
get {
|
||||
return ResourceManager.GetString("BotInvalidPasswordDuringLogin", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wyszukuje zlokalizowany ciąg podobny do ciągu Bot has level {0}..
|
||||
/// </summary>
|
||||
@@ -691,6 +700,15 @@ namespace ArchiSteamFarm.Localization {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wyszukuje zlokalizowany ciąg podobny do ciągu The trade offer {0} is determined to be {1} due to {2}..
|
||||
/// </summary>
|
||||
public static string BotTradeOfferResult {
|
||||
get {
|
||||
return ResourceManager.GetString("BotTradeOfferResult", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wyszukuje zlokalizowany ciąg podobny do ciągu Unable to login to Steam: {0}/{1}.
|
||||
/// </summary>
|
||||
|
||||
@@ -721,4 +721,6 @@
|
||||
<data name="IPCConfigChanged" xml:space="preserve">
|
||||
<value>IPC настройките са били променени!</value>
|
||||
</data>
|
||||
|
||||
|
||||
</root>
|
||||
@@ -720,4 +720,6 @@ StackTrace:
|
||||
<data name="IPCConfigChanged" xml:space="preserve">
|
||||
<value>IPC nastavení bylo změněno!</value>
|
||||
</data>
|
||||
|
||||
|
||||
</root>
|
||||
@@ -720,4 +720,6 @@ StackTrace:
|
||||
<data name="IPCConfigChanged" xml:space="preserve">
|
||||
<value>IPC konfiguration er blevet ændret!</value>
|
||||
</data>
|
||||
|
||||
|
||||
</root>
|
||||
@@ -724,4 +724,6 @@ Prozesslaufzeit: {1}</value>
|
||||
<data name="IPCConfigChanged" xml:space="preserve">
|
||||
<value>Die IPC-Konfiguration wurde geändert!</value>
|
||||
</data>
|
||||
|
||||
|
||||
</root>
|
||||
@@ -689,4 +689,6 @@ StackTrace:
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
|
||||
</root>
|
||||
@@ -723,4 +723,12 @@ Tiempo de actividad del proceso: {1}</value>
|
||||
<data name="IPCConfigChanged" xml:space="preserve">
|
||||
<value>¡La configuración IPC ha sido cambiada!</value>
|
||||
</data>
|
||||
<data name="BotTradeOfferResult" xml:space="preserve">
|
||||
<value>La oferta de intercambio {0} se determina a ser {1} debido a {2}.</value>
|
||||
<comment>{0} will be replaced by trade offer ID (number), {1} will be replaced by internal ASF enum name, {2} will be replaced by technical reason why the trade was determined to be in this state</comment>
|
||||
</data>
|
||||
<data name="BotInvalidPasswordDuringLogin" xml:space="preserve">
|
||||
<value>Recibido el código de error InvalidPassword {0} veces seguidas. Es posible que la contraseña para esta cuenta sea incorrecta, ¡abortando!</value>
|
||||
<comment>{0} will be replaced by maximum allowed number of failed login attempts</comment>
|
||||
</data>
|
||||
</root>
|
||||
@@ -714,4 +714,6 @@ StackTrace:
|
||||
<data name="IPCConfigChanged" xml:space="preserve">
|
||||
<value>IPC asetusta on muutettu!</value>
|
||||
</data>
|
||||
|
||||
|
||||
</root>
|
||||
@@ -724,4 +724,6 @@ Durée de fonctionnement : {1}</value>
|
||||
<data name="IPCConfigChanged" xml:space="preserve">
|
||||
<value>La configuration IPC a été modifiée !</value>
|
||||
</data>
|
||||
|
||||
|
||||
</root>
|
||||
@@ -639,6 +639,8 @@ StackTrace:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -718,4 +718,6 @@ StackTrace: {2}</value>
|
||||
<data name="IPCConfigChanged" xml:space="preserve">
|
||||
<value>Az IPC konfig megváltozott!</value>
|
||||
</data>
|
||||
|
||||
|
||||
</root>
|
||||
@@ -670,4 +670,6 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</root>
|
||||
@@ -718,4 +718,6 @@
|
||||
<data name="IPCConfigChanged" xml:space="preserve">
|
||||
<value>Configurazione IPC modificata!</value>
|
||||
</data>
|
||||
|
||||
|
||||
</root>
|
||||
@@ -713,4 +713,6 @@
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
|
||||
</root>
|
||||
@@ -720,4 +720,6 @@ StackTrace:
|
||||
<data name="IPCConfigChanged" xml:space="preserve">
|
||||
<value>IPC 환경설정이 변경되었습니다.</value>
|
||||
</data>
|
||||
|
||||
|
||||
</root>
|
||||
@@ -720,4 +720,6 @@ StackTrace:
|
||||
<data name="IPCConfigChanged" xml:space="preserve">
|
||||
<value>IPC konfigūracija buvo pakeista!</value>
|
||||
</data>
|
||||
|
||||
|
||||
</root>
|
||||
@@ -723,4 +723,9 @@ Proces uptime: {1}</value>
|
||||
<data name="IPCConfigChanged" xml:space="preserve">
|
||||
<value>IPC-configuratie is gewijzigd!</value>
|
||||
</data>
|
||||
<data name="BotTradeOfferResult" xml:space="preserve">
|
||||
<value>Het ruilaanbod {0} is als {1} gemarkeerd vanwege {2}.</value>
|
||||
<comment>{0} will be replaced by trade offer ID (number), {1} will be replaced by internal ASF enum name, {2} will be replaced by technical reason why the trade was determined to be in this state</comment>
|
||||
</data>
|
||||
|
||||
</root>
|
||||
@@ -724,4 +724,12 @@ Czas procesu: {1}</value>
|
||||
<data name="IPCConfigChanged" xml:space="preserve">
|
||||
<value>Konfiguracja IPC została zmieniona!</value>
|
||||
</data>
|
||||
<data name="BotTradeOfferResult" xml:space="preserve">
|
||||
<value>Oferta wymiany {0} jest ustalona jako {1} z powodu {2}.</value>
|
||||
<comment>{0} will be replaced by trade offer ID (number), {1} will be replaced by internal ASF enum name, {2} will be replaced by technical reason why the trade was determined to be in this state</comment>
|
||||
</data>
|
||||
<data name="BotInvalidPasswordDuringLogin" xml:space="preserve">
|
||||
<value>Otrzymano kod błędu InvalidPassword {0} razy z rzędu. Twoje hasło do tego konta jest najprawdopodobniej błędne, przerywam!</value>
|
||||
<comment>{0} will be replaced by maximum allowed number of failed login attempts</comment>
|
||||
</data>
|
||||
</root>
|
||||
@@ -724,4 +724,12 @@ Tempo de execução: {1}</value>
|
||||
<data name="IPCConfigChanged" xml:space="preserve">
|
||||
<value>A configuração IPC foi alterada!</value>
|
||||
</data>
|
||||
<data name="BotTradeOfferResult" xml:space="preserve">
|
||||
<value>A oferta de troca {0} será determinada como {1} devido à {2}.</value>
|
||||
<comment>{0} will be replaced by trade offer ID (number), {1} will be replaced by internal ASF enum name, {2} will be replaced by technical reason why the trade was determined to be in this state</comment>
|
||||
</data>
|
||||
<data name="BotInvalidPasswordDuringLogin" xml:space="preserve">
|
||||
<value>O código de erro InvalidPassword foi recebido {0} vezes seguidas. A sua senha para essa conta provavelmente está errada, abortando!</value>
|
||||
<comment>{0} will be replaced by maximum allowed number of failed login attempts</comment>
|
||||
</data>
|
||||
</root>
|
||||
@@ -554,4 +554,6 @@ StackTrace:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</root>
|
||||
@@ -724,4 +724,12 @@ Process uptime: {1}</value>
|
||||
<data name="IPCConfigChanged" xml:space="preserve">
|
||||
<value>IPC config has been changed!</value>
|
||||
</data>
|
||||
<data name="BotTradeOfferResult" xml:space="preserve">
|
||||
<value>The trade offer {0} is determined to be {1} due to {2}.</value>
|
||||
<comment>{0} will be replaced by trade offer ID (number), {1} will be replaced by internal ASF enum name, {2} will be replaced by technical reason why the trade was determined to be in this state</comment>
|
||||
</data>
|
||||
<data name="BotInvalidPasswordDuringLogin" xml:space="preserve">
|
||||
<value>Received InvalidPassword error code {0} times in a row. Your password for this account is most likely wrong, aborting!</value>
|
||||
<comment>{0} will be replaced by maximum allowed number of failed login attempts</comment>
|
||||
</data>
|
||||
</root>
|
||||
@@ -724,4 +724,6 @@ Proces: {1}</value>
|
||||
<data name="IPCConfigChanged" xml:space="preserve">
|
||||
<value>Configurația IPC a fost schimbată!</value>
|
||||
</data>
|
||||
|
||||
|
||||
</root>
|
||||
@@ -724,4 +724,9 @@
|
||||
<data name="IPCConfigChanged" xml:space="preserve">
|
||||
<value>Конфигурация IPC была изменена!</value>
|
||||
</data>
|
||||
<data name="BotTradeOfferResult" xml:space="preserve">
|
||||
<value>По предложению обмена {0} принято решение {1} по причине {2}.</value>
|
||||
<comment>{0} will be replaced by trade offer ID (number), {1} will be replaced by internal ASF enum name, {2} will be replaced by technical reason why the trade was determined to be in this state</comment>
|
||||
</data>
|
||||
|
||||
</root>
|
||||
@@ -702,4 +702,6 @@ Interaktívna konzola je teraz aktívna, napíšte "c" pre vstup do príkazovéh
|
||||
<data name="IPCConfigChanged" xml:space="preserve">
|
||||
<value>Konfigurácia IPC bola zmenená!</value>
|
||||
</data>
|
||||
|
||||
|
||||
</root>
|
||||
@@ -568,6 +568,8 @@ StackTrace:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -621,6 +621,8 @@ StackTrace:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -724,4 +724,6 @@ Süreç çalışma zamanı: {1}</value>
|
||||
<data name="IPCConfigChanged" xml:space="preserve">
|
||||
<value>IPC yapılandırması değiştirildi!</value>
|
||||
</data>
|
||||
|
||||
|
||||
</root>
|
||||
@@ -724,4 +724,9 @@
|
||||
<data name="IPCConfigChanged" xml:space="preserve">
|
||||
<value>Конфігурацію IPC було змінено!</value>
|
||||
</data>
|
||||
<data name="BotTradeOfferResult" xml:space="preserve">
|
||||
<value>Пропозицію обміну {0} вирішено {1} через {2}.</value>
|
||||
<comment>{0} will be replaced by trade offer ID (number), {1} will be replaced by internal ASF enum name, {2} will be replaced by technical reason why the trade was determined to be in this state</comment>
|
||||
</data>
|
||||
|
||||
</root>
|
||||
@@ -720,4 +720,6 @@ StackTrace:
|
||||
<data name="IPCConfigChanged" xml:space="preserve">
|
||||
<value>Cấu hình IPC đã được thay đổi!</value>
|
||||
</data>
|
||||
|
||||
|
||||
</root>
|
||||
@@ -724,4 +724,12 @@
|
||||
<data name="IPCConfigChanged" xml:space="preserve">
|
||||
<value>IPC 配置文件已被更改!</value>
|
||||
</data>
|
||||
<data name="BotTradeOfferResult" xml:space="preserve">
|
||||
<value>交易报价 {0} 被确定为 {1},原因是:{2}。</value>
|
||||
<comment>{0} will be replaced by trade offer ID (number), {1} will be replaced by internal ASF enum name, {2} will be replaced by technical reason why the trade was determined to be in this state</comment>
|
||||
</data>
|
||||
<data name="BotInvalidPasswordDuringLogin" xml:space="preserve">
|
||||
<value>连续收到无效密码(InvalidPassword)错误代码 {0} 次。您对此帐户的密码很可能是错误的,正在中止操作!</value>
|
||||
<comment>{0} will be replaced by maximum allowed number of failed login attempts</comment>
|
||||
</data>
|
||||
</root>
|
||||
@@ -720,4 +720,6 @@
|
||||
<data name="IPCConfigChanged" xml:space="preserve">
|
||||
<value>IPC 設定檔已變更!</value>
|
||||
</data>
|
||||
|
||||
|
||||
</root>
|
||||
@@ -723,4 +723,6 @@
|
||||
<data name="IPCConfigChanged" xml:space="preserve">
|
||||
<value>IPC 設定檔已變更!</value>
|
||||
</data>
|
||||
|
||||
|
||||
</root>
|
||||
@@ -180,7 +180,9 @@ namespace ArchiSteamFarm {
|
||||
|
||||
try {
|
||||
inventory = await Bot.ArchiWebHandler.GetInventoryAsync().Where(item => item.Tradable && acceptedMatchableTypes.Contains(item.Type)).ToHashSetAsync().ConfigureAwait(false);
|
||||
} catch (HttpRequestException) {
|
||||
} catch (HttpRequestException e) {
|
||||
Bot.ArchiLogger.LogGenericWarningException(e);
|
||||
|
||||
// This is actually inventory failure, so we'll stop sending heartbeats but not record it as valid check
|
||||
ShouldSendHeartBeats = false;
|
||||
|
||||
@@ -282,7 +284,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
// Bot must have at least one accepted matchable type set
|
||||
if ((Bot.BotConfig.MatchableTypes.Count == 0) || Bot.BotConfig.MatchableTypes.All(type => !AcceptedMatchableTypes.Contains(type))) {
|
||||
Bot.ArchiLogger.LogGenericTrace(string.Format(Strings.WarningFailedWithError, nameof(Bot.BotConfig.MatchableTypes) + ": " + Bot.BotConfig.MatchableTypes));
|
||||
Bot.ArchiLogger.LogGenericTrace(string.Format(Strings.WarningFailedWithError, nameof(Bot.BotConfig.MatchableTypes) + ": " + string.Join(", ", Bot.BotConfig.MatchableTypes)));
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -364,7 +366,9 @@ namespace ArchiSteamFarm {
|
||||
|
||||
try {
|
||||
ourInventory = await Bot.ArchiWebHandler.GetInventoryAsync().Where(item => acceptedMatchableTypes.Contains(item.Type)).ToHashSetAsync().ConfigureAwait(false);
|
||||
} catch (HttpRequestException) {
|
||||
} catch (HttpRequestException e) {
|
||||
Bot.ArchiLogger.LogGenericWarningException(e);
|
||||
|
||||
return (false, false);
|
||||
} catch (Exception e) {
|
||||
Bot.ArchiLogger.LogGenericException(e);
|
||||
@@ -412,11 +416,29 @@ namespace ArchiSteamFarm {
|
||||
|
||||
Bot.ArchiLogger.LogGenericTrace(listedUser.SteamID + "...");
|
||||
|
||||
byte? holdDuration = await Bot.ArchiWebHandler.GetTradeHoldDurationForUser(listedUser.SteamID, listedUser.TradeToken).ConfigureAwait(false);
|
||||
|
||||
if (!holdDuration.HasValue) {
|
||||
Bot.ArchiLogger.LogGenericTrace(string.Format(Strings.ErrorIsEmpty, nameof(holdDuration)));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (holdDuration.Value > 0) {
|
||||
if (holdDuration.Value > ASF.GlobalConfig.MaxTradeHoldDuration) {
|
||||
Bot.ArchiLogger.LogGenericTrace(holdDuration.Value + " > " + ASF.GlobalConfig.MaxTradeHoldDuration);
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
HashSet<Steam.Asset> theirInventory;
|
||||
|
||||
try {
|
||||
theirInventory = await Bot.ArchiWebHandler.GetInventoryAsync(listedUser.SteamID).Where(item => (!listedUser.MatchEverything || item.Tradable) && wantedSets.Contains((item.RealAppID, item.Type, item.Rarity))).ToHashSetAsync().ConfigureAwait(false);
|
||||
} catch (HttpRequestException) {
|
||||
theirInventory = await Bot.ArchiWebHandler.GetInventoryAsync(listedUser.SteamID).Where(item => (!listedUser.MatchEverything || item.Tradable) && wantedSets.Contains((item.RealAppID, item.Type, item.Rarity)) && ((holdDuration.Value == 0) || !(((item.Type == Steam.Asset.EType.FoilTradingCard) || (item.Type == Steam.Asset.EType.TradingCard)) && CardsFarmer.SalesBlacklist.Contains(item.RealAppID)))).ToHashSetAsync().ConfigureAwait(false);
|
||||
} catch (HttpRequestException e) {
|
||||
Bot.ArchiLogger.LogGenericWarningException(e);
|
||||
|
||||
continue;
|
||||
} catch (Exception e) {
|
||||
Bot.ArchiLogger.LogGenericException(e);
|
||||
|
||||
@@ -527,11 +527,15 @@ namespace ArchiSteamFarm {
|
||||
if (tradeOffer.OtherSteamID64 != 0) {
|
||||
// Always accept trades from SteamMasterID
|
||||
if (Bot.HasPermission(tradeOffer.OtherSteamID64, BotConfig.EPermission.Master)) {
|
||||
Bot.ArchiLogger.LogGenericDebug(string.Format(Strings.BotTradeOfferResult, tradeOffer.TradeOfferID, ParseTradeResult.EResult.Accepted, nameof(tradeOffer.OtherSteamID64) + " " + tradeOffer.OtherSteamID64 + ": " + BotConfig.EPermission.Master));
|
||||
|
||||
return ParseTradeResult.EResult.Accepted;
|
||||
}
|
||||
|
||||
// Always deny trades from blacklisted steamIDs
|
||||
if (Bot.IsBlacklistedFromTrades(tradeOffer.OtherSteamID64)) {
|
||||
Bot.ArchiLogger.LogGenericDebug(string.Format(Strings.BotTradeOfferResult, tradeOffer.TradeOfferID, ParseTradeResult.EResult.Blacklisted, nameof(tradeOffer.OtherSteamID64) + " " + tradeOffer.OtherSteamID64));
|
||||
|
||||
return ParseTradeResult.EResult.Blacklisted;
|
||||
}
|
||||
}
|
||||
@@ -540,6 +544,8 @@ namespace ArchiSteamFarm {
|
||||
switch (tradeOffer.ItemsToGive.Count) {
|
||||
case 0 when tradeOffer.ItemsToReceive.Count == 0:
|
||||
// If it's steam issue, try again later
|
||||
Bot.ArchiLogger.LogGenericDebug(string.Format(Strings.BotTradeOfferResult, tradeOffer.TradeOfferID, ParseTradeResult.EResult.TryAgain, nameof(tradeOffer.ItemsToReceive.Count) + " = 0"));
|
||||
|
||||
return ParseTradeResult.EResult.TryAgain;
|
||||
case 0:
|
||||
// Otherwise react accordingly, depending on our preference
|
||||
@@ -548,32 +554,46 @@ namespace ArchiSteamFarm {
|
||||
|
||||
// If we accept donations and bot trades, accept it right away
|
||||
if (acceptDonations && acceptBotTrades) {
|
||||
Bot.ArchiLogger.LogGenericDebug(string.Format(Strings.BotTradeOfferResult, tradeOffer.TradeOfferID, ParseTradeResult.EResult.Accepted, nameof(acceptDonations) + " = " + true + " && " + nameof(acceptBotTrades) + " = " + true));
|
||||
|
||||
return ParseTradeResult.EResult.Accepted;
|
||||
}
|
||||
|
||||
// If we don't accept donations, neither bot trades, deny it right away
|
||||
if (!acceptDonations && !acceptBotTrades) {
|
||||
Bot.ArchiLogger.LogGenericDebug(string.Format(Strings.BotTradeOfferResult, tradeOffer.TradeOfferID, ParseTradeResult.EResult.Rejected, nameof(acceptDonations) + " = " + false + " && " + nameof(acceptBotTrades) + " = " + false));
|
||||
|
||||
return ParseTradeResult.EResult.Rejected;
|
||||
}
|
||||
|
||||
// Otherwise we either accept donations but not bot trades, or we accept bot trades but not donations
|
||||
bool isBotTrade = (tradeOffer.OtherSteamID64 != 0) && Bot.Bots.Values.Any(bot => bot.SteamID == tradeOffer.OtherSteamID64);
|
||||
|
||||
return (acceptDonations && !isBotTrade) || (acceptBotTrades && isBotTrade) ? ParseTradeResult.EResult.Accepted : ParseTradeResult.EResult.Rejected;
|
||||
ParseTradeResult.EResult result = (acceptDonations && !isBotTrade) || (acceptBotTrades && isBotTrade) ? ParseTradeResult.EResult.Accepted : ParseTradeResult.EResult.Rejected;
|
||||
|
||||
Bot.ArchiLogger.LogGenericDebug(string.Format(Strings.BotTradeOfferResult, tradeOffer.TradeOfferID, result, nameof(acceptDonations) + " = " + acceptDonations + " && " + nameof(acceptBotTrades) + " = " + acceptBotTrades + " && " + nameof(isBotTrade) + " = " + isBotTrade));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// If we don't have SteamTradeMatcher enabled, this is the end for us
|
||||
if (!Bot.BotConfig.TradingPreferences.HasFlag(BotConfig.ETradingPreferences.SteamTradeMatcher)) {
|
||||
Bot.ArchiLogger.LogGenericDebug(string.Format(Strings.BotTradeOfferResult, tradeOffer.TradeOfferID, ParseTradeResult.EResult.Rejected, nameof(BotConfig.ETradingPreferences.SteamTradeMatcher) + " = " + false));
|
||||
|
||||
return ParseTradeResult.EResult.Rejected;
|
||||
}
|
||||
|
||||
// Decline trade if we're giving more count-wise, this is a very naive pre-check, it'll be strengthened in more detailed fair types exchange next
|
||||
if (tradeOffer.ItemsToGive.Count > tradeOffer.ItemsToReceive.Count) {
|
||||
Bot.ArchiLogger.LogGenericDebug(string.Format(Strings.BotTradeOfferResult, tradeOffer.TradeOfferID, ParseTradeResult.EResult.Rejected, nameof(tradeOffer.ItemsToGive.Count) + ": " + tradeOffer.ItemsToGive.Count + " > " + tradeOffer.ItemsToReceive.Count));
|
||||
|
||||
return ParseTradeResult.EResult.Rejected;
|
||||
}
|
||||
|
||||
// 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) || !IsFairExchange(tradeOffer.ItemsToGive, tradeOffer.ItemsToReceive)) {
|
||||
Bot.ArchiLogger.LogGenericDebug(string.Format(Strings.BotTradeOfferResult, tradeOffer.TradeOfferID, ParseTradeResult.EResult.Rejected, nameof(tradeOffer.IsValidSteamItemsRequest) + " || " + nameof(IsFairExchange)));
|
||||
|
||||
return ParseTradeResult.EResult.Rejected;
|
||||
}
|
||||
|
||||
@@ -584,6 +604,8 @@ namespace ArchiSteamFarm {
|
||||
|
||||
if (!holdDuration.HasValue) {
|
||||
// If we can't get trade hold duration, try again later
|
||||
Bot.ArchiLogger.LogGenericDebug(string.Format(Strings.BotTradeOfferResult, tradeOffer.TradeOfferID, ParseTradeResult.EResult.TryAgain, nameof(holdDuration)));
|
||||
|
||||
return ParseTradeResult.EResult.TryAgain;
|
||||
}
|
||||
|
||||
@@ -591,12 +613,16 @@ namespace ArchiSteamFarm {
|
||||
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 > ASF.GlobalConfig.MaxTradeHoldDuration) || tradeOffer.ItemsToGive.Any(item => ((item.Type == Steam.Asset.EType.FoilTradingCard) || (item.Type == Steam.Asset.EType.TradingCard)) && CardsFarmer.SalesBlacklist.Contains(item.RealAppID))) {
|
||||
Bot.ArchiLogger.LogGenericDebug(string.Format(Strings.BotTradeOfferResult, tradeOffer.TradeOfferID, ParseTradeResult.EResult.Rejected, nameof(holdDuration) + " > 0: " + holdDuration.Value));
|
||||
|
||||
return ParseTradeResult.EResult.Rejected;
|
||||
}
|
||||
}
|
||||
|
||||
// If we're matching everything, this is enough for us
|
||||
if (Bot.BotConfig.TradingPreferences.HasFlag(BotConfig.ETradingPreferences.MatchEverything)) {
|
||||
Bot.ArchiLogger.LogGenericDebug(string.Format(Strings.BotTradeOfferResult, tradeOffer.TradeOfferID, ParseTradeResult.EResult.Accepted, BotConfig.ETradingPreferences.MatchEverything));
|
||||
|
||||
return ParseTradeResult.EResult.Accepted;
|
||||
}
|
||||
|
||||
@@ -612,12 +638,16 @@ namespace ArchiSteamFarm {
|
||||
|
||||
try {
|
||||
inventory = await Bot.ArchiWebHandler.GetInventoryAsync(Bot.SteamID).Where(item => wantedSets.Contains((item.RealAppID, item.Type, item.Rarity))).ToHashSetAsync().ConfigureAwait(false);
|
||||
} catch (HttpRequestException) {
|
||||
} catch (HttpRequestException e) {
|
||||
// If we can't check our inventory when not using MatchEverything, this is a temporary failure, try again later
|
||||
Bot.ArchiLogger.LogGenericWarningException(e);
|
||||
Bot.ArchiLogger.LogGenericDebug(string.Format(Strings.BotTradeOfferResult, tradeOffer.TradeOfferID, ParseTradeResult.EResult.TryAgain, nameof(inventory)));
|
||||
|
||||
return ParseTradeResult.EResult.TryAgain;
|
||||
} catch (Exception e) {
|
||||
// If we can't check our inventory when not using MatchEverything, this is a temporary failure, try again later
|
||||
Bot.ArchiLogger.LogGenericException(e);
|
||||
Bot.ArchiLogger.LogGenericDebug(string.Format(Strings.BotTradeOfferResult, tradeOffer.TradeOfferID, ParseTradeResult.EResult.TryAgain, nameof(inventory)));
|
||||
|
||||
return ParseTradeResult.EResult.TryAgain;
|
||||
}
|
||||
@@ -625,6 +655,7 @@ namespace ArchiSteamFarm {
|
||||
if (inventory.Count == 0) {
|
||||
// If we can't check our inventory when not using MatchEverything, this is a temporary failure, try again later
|
||||
Bot.ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorIsEmpty, nameof(inventory)));
|
||||
Bot.ArchiLogger.LogGenericDebug(string.Format(Strings.BotTradeOfferResult, tradeOffer.TradeOfferID, ParseTradeResult.EResult.TryAgain, nameof(inventory)));
|
||||
|
||||
return ParseTradeResult.EResult.TryAgain;
|
||||
}
|
||||
@@ -632,7 +663,11 @@ namespace ArchiSteamFarm {
|
||||
bool accept = IsTradeNeutralOrBetter(inventory, tradeOffer.ItemsToGive.Select(item => item.CreateShallowCopy()).ToHashSet(), tradeOffer.ItemsToReceive.Select(item => item.CreateShallowCopy()).ToHashSet());
|
||||
|
||||
// We're now sure whether the trade is neutral+ for us or not
|
||||
return accept ? ParseTradeResult.EResult.Accepted : ParseTradeResult.EResult.Rejected;
|
||||
ParseTradeResult.EResult acceptResult = accept ? ParseTradeResult.EResult.Accepted : ParseTradeResult.EResult.Rejected;
|
||||
|
||||
Bot.ArchiLogger.LogGenericDebug(string.Format(Strings.BotTradeOfferResult, tradeOffer.TradeOfferID, acceptResult, nameof(IsTradeNeutralOrBetter)));
|
||||
|
||||
return acceptResult;
|
||||
}
|
||||
|
||||
public sealed class ParseTradeResult {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<Version>4.2.3.7</Version>
|
||||
<Version>4.2.4.0</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
|
||||
$archiCrowdinPath = 'tools\ArchiCrowdin'
|
||||
$archiCrowdinScriptPath = "$archiCrowdinPath\archi.ps1"
|
||||
|
||||
$crowdinConfigFileName = 'crowdin.yml'
|
||||
$crowdinIdentityFileName = 'crowdin_identity.yml'
|
||||
$crowdinIdentityPath = "$archiCrowdinPath\$crowdinIdentityFileName"
|
||||
|
||||
$archiTargets = @('ASF-ui', 'ASF-WebConfigGenerator')
|
||||
|
||||
Push-Location "$PSScriptRoot"
|
||||
|
||||
try {
|
||||
for ($i = 0; ($i -lt 3) -and (!(Test-Path "$crowdinConfigFileName" -PathType Leaf)); $i++) {
|
||||
Set-Location ..
|
||||
}
|
||||
|
||||
if (!(Test-Path "$crowdinConfigFileName" -PathType Leaf)) {
|
||||
throw "$crowdinConfigFileName could not be found, aborting."
|
||||
}
|
||||
|
||||
if (!(Test-Path "$archiCrowdinScriptPath" -PathType Leaf)) {
|
||||
throw "$archiCrowdinScriptPath could not be found, aborting."
|
||||
}
|
||||
|
||||
if (!(Test-Path "$crowdinIdentityPath" -PathType Leaf)) {
|
||||
throw "$crowdinIdentityPath could not be found, aborting."
|
||||
}
|
||||
|
||||
& "$archiCrowdinScriptPath" -t:$archiTargets -d -c -p # Download and commit all independent submodules that are part of ASF project
|
||||
& "$archiCrowdinScriptPath" -t:wiki -c -p # Wiki submodule depends on us, we do this one more time before in order to ensure that tree is up-to-date (e.g. branch is master before we start modifying files in the next step)
|
||||
& "$archiCrowdinScriptPath" -d -p -rs:no # Download translations for the main project, which also includes wiki submodule as of now
|
||||
& "$archiCrowdinScriptPath" -t:wiki -c -p # Commit the wiki submodule that we updated in the previous step
|
||||
& "$archiCrowdinScriptPath" -c # Commit the main project and references of all submodules
|
||||
} finally {
|
||||
Pop-Location
|
||||
}
|
||||
|
||||
pause
|
||||
2
wiki
2
wiki
Submodule wiki updated: 6a123c4dda...cf5d49a9de
Reference in New Issue
Block a user