mirror of
https://github.com/JustArchiNET/ArchiSteamFarm.git
synced 2025-12-16 14:30:31 +00:00
R# code improvements & cleanups
This commit is contained in:
@@ -28,28 +28,6 @@ using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
namespace ArchiSteamFarm.Tests {
|
||||
[TestClass]
|
||||
public sealed class Bot {
|
||||
[TestMethod]
|
||||
public void MoreCardsThanNeeded() {
|
||||
const uint appID = 42;
|
||||
|
||||
HashSet<Steam.Asset> items = new() {
|
||||
CreateCard(1, appID),
|
||||
CreateCard(1, appID),
|
||||
CreateCard(2, appID),
|
||||
CreateCard(3, appID)
|
||||
};
|
||||
|
||||
HashSet<Steam.Asset> itemsToSend = GetItemsForFullBadge(items, 3, appID);
|
||||
|
||||
Dictionary<(uint RealAppID, ulong ContextID, ulong ClassID), uint> expectedResult = new() {
|
||||
{ (appID, Steam.Asset.SteamCommunityContextID, 1), 1 },
|
||||
{ (appID, Steam.Asset.SteamCommunityContextID, 2), 1 },
|
||||
{ (appID, Steam.Asset.SteamCommunityContextID, 3), 1 }
|
||||
};
|
||||
|
||||
AssertResultMatchesExpectation(expectedResult, itemsToSend);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MaxItemsBarelyEnoughForOneSet() {
|
||||
const uint relevantAppID = 42;
|
||||
@@ -92,43 +70,25 @@ namespace ArchiSteamFarm.Tests {
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TooManyCardsForSingleTrade() {
|
||||
public void MoreCardsThanNeeded() {
|
||||
const uint appID = 42;
|
||||
|
||||
HashSet<Steam.Asset> items = new();
|
||||
|
||||
for (byte i = 0; i < ArchiSteamFarm.Trading.MaxItemsPerTrade; i++) {
|
||||
items.Add(CreateCard(1, appID));
|
||||
items.Add(CreateCard(2, appID));
|
||||
}
|
||||
|
||||
HashSet<Steam.Asset> itemsToSend = GetItemsForFullBadge(items, 2, appID);
|
||||
|
||||
Assert.IsTrue(itemsToSend.Count <= ArchiSteamFarm.Trading.MaxItemsPerTrade);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TooManyCardsForSingleTradeMultipleAppIDs() {
|
||||
const uint appID0 = 42;
|
||||
const uint appID1 = 43;
|
||||
|
||||
HashSet<Steam.Asset> items = new();
|
||||
|
||||
for (byte i = 0; i < 100; i++) {
|
||||
items.Add(CreateCard(1, appID0));
|
||||
items.Add(CreateCard(2, appID0));
|
||||
items.Add(CreateCard(1, appID1));
|
||||
items.Add(CreateCard(2, appID1));
|
||||
}
|
||||
|
||||
Dictionary<uint, byte> itemsPerSet = new() {
|
||||
{ appID0, 2 },
|
||||
{ appID1, 2 }
|
||||
HashSet<Steam.Asset> items = new() {
|
||||
CreateCard(1, appID),
|
||||
CreateCard(1, appID),
|
||||
CreateCard(2, appID),
|
||||
CreateCard(3, appID)
|
||||
};
|
||||
|
||||
HashSet<Steam.Asset> itemsToSend = GetItemsForFullBadge(items, itemsPerSet);
|
||||
HashSet<Steam.Asset> itemsToSend = GetItemsForFullBadge(items, 3, appID);
|
||||
|
||||
Assert.IsTrue(itemsToSend.Count <= ArchiSteamFarm.Trading.MaxItemsPerTrade);
|
||||
Dictionary<(uint RealAppID, ulong ContextID, ulong ClassID), uint> expectedResult = new() {
|
||||
{ (appID, Steam.Asset.SteamCommunityContextID, 1), 1 },
|
||||
{ (appID, Steam.Asset.SteamCommunityContextID, 2), 1 },
|
||||
{ (appID, Steam.Asset.SteamCommunityContextID, 3), 1 }
|
||||
};
|
||||
|
||||
AssertResultMatchesExpectation(expectedResult, itemsToSend);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -459,6 +419,46 @@ namespace ArchiSteamFarm.Tests {
|
||||
AssertResultMatchesExpectation(expectedResult, itemsToSend);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TooManyCardsForSingleTrade() {
|
||||
const uint appID = 42;
|
||||
|
||||
HashSet<Steam.Asset> items = new();
|
||||
|
||||
for (byte i = 0; i < ArchiSteamFarm.Trading.MaxItemsPerTrade; i++) {
|
||||
items.Add(CreateCard(1, appID));
|
||||
items.Add(CreateCard(2, appID));
|
||||
}
|
||||
|
||||
HashSet<Steam.Asset> itemsToSend = GetItemsForFullBadge(items, 2, appID);
|
||||
|
||||
Assert.IsTrue(itemsToSend.Count <= ArchiSteamFarm.Trading.MaxItemsPerTrade);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TooManyCardsForSingleTradeMultipleAppIDs() {
|
||||
const uint appID0 = 42;
|
||||
const uint appID1 = 43;
|
||||
|
||||
HashSet<Steam.Asset> items = new();
|
||||
|
||||
for (byte i = 0; i < 100; i++) {
|
||||
items.Add(CreateCard(1, appID0));
|
||||
items.Add(CreateCard(2, appID0));
|
||||
items.Add(CreateCard(1, appID1));
|
||||
items.Add(CreateCard(2, appID1));
|
||||
}
|
||||
|
||||
Dictionary<uint, byte> itemsPerSet = new() {
|
||||
{ appID0, 2 },
|
||||
{ appID1, 2 }
|
||||
};
|
||||
|
||||
HashSet<Steam.Asset> itemsToSend = GetItemsForFullBadge(items, itemsPerSet);
|
||||
|
||||
Assert.IsTrue(itemsToSend.Count <= ArchiSteamFarm.Trading.MaxItemsPerTrade);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(InvalidOperationException))]
|
||||
public void TooManyCardsPerSet() {
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -835,7 +835,7 @@ namespace ArchiSteamFarm {
|
||||
// Ensure that we ask for a list of servers if we don't have any saved servers available
|
||||
IEnumerable<ServerRecord> servers = await GlobalDatabase.ServerListProvider.FetchServerListAsync().ConfigureAwait(false);
|
||||
|
||||
if (servers?.Any() != true) {
|
||||
if (!servers.Any()) {
|
||||
ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Strings.Initializing, nameof(SteamDirectory)));
|
||||
|
||||
SteamConfiguration steamConfiguration = SteamConfiguration.Create(builder => builder.WithProtocolTypes(GlobalConfig.SteamProtocols).WithCellID(GlobalDatabase.CellID).WithServerListProvider(GlobalDatabase.ServerListProvider).WithHttpClientFactory(() => WebBrowser.GenerateDisposableHttpClient()));
|
||||
|
||||
@@ -2193,10 +2193,6 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
if (response == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
string? steamLogin = response["token"].AsString();
|
||||
|
||||
if (string.IsNullOrEmpty(steamLogin)) {
|
||||
@@ -2475,11 +2471,9 @@ namespace ArchiSteamFarm {
|
||||
// The page should have as little internal dependencies as possible, since every extra chunk increases likelihood of broken functionality. We can only make a guess here based on the amount of content that the page returns to us
|
||||
// It should also be URL with fairly fixed address that isn't going to disappear anytime soon, preferably something staple that is a dependency of other requests, so it's very unlikely to change in a way that would add overhead in the future
|
||||
// Lastly, it should be a request that is preferably generic enough as a routine check, not something specialized and targetted, to make it very clear that we're just checking if session is up, and to further aid internal dependencies specified above by rendering as general Steam info as possible
|
||||
|
||||
const string host = SteamStoreURL;
|
||||
const string request = "/account";
|
||||
|
||||
WebBrowser.BasicResponse? response = await WebLimitRequest(host, async () => await WebBrowser.UrlHead(host + request).ConfigureAwait(false)).ConfigureAwait(false);
|
||||
WebBrowser.BasicResponse? response = await WebLimitRequest(SteamStoreURL, async () => await WebBrowser.UrlHead(SteamStoreURL + request).ConfigureAwait(false)).ConfigureAwait(false);
|
||||
|
||||
if (response == null) {
|
||||
return null;
|
||||
|
||||
@@ -625,7 +625,7 @@ namespace ArchiSteamFarm {
|
||||
HashSet<Task<HashSet<uint>?>> tasks = new(maxPages - 1);
|
||||
|
||||
for (byte page = 2; page <= maxPages; page++) {
|
||||
// We need a copy of variable being passed when in for loops, as loop will proceed before our task is launched
|
||||
// ReSharper disable once InlineTemporaryVariable - we need a copy of variable being passed when in for loops, as loop will proceed before our task is launched
|
||||
byte currentPage = page;
|
||||
tasks.Add(GetPossiblyCompletedBadgeAppIDs(currentPage));
|
||||
}
|
||||
@@ -1900,6 +1900,61 @@ namespace ArchiSteamFarm {
|
||||
return keys;
|
||||
}
|
||||
|
||||
private async Task<HashSet<uint>?> GetPossiblyCompletedBadgeAppIDs(byte page) {
|
||||
if (page == 0) {
|
||||
throw new ArgumentOutOfRangeException(nameof(page));
|
||||
}
|
||||
|
||||
using IDocument? badgePage = await ArchiWebHandler.GetBadgePage(page).ConfigureAwait(false);
|
||||
|
||||
if (badgePage == null) {
|
||||
ArchiLogger.LogGenericWarning(Strings.WarningCouldNotCheckBadges);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return GetPossiblyCompletedBadgeAppIDs(badgePage);
|
||||
}
|
||||
|
||||
private HashSet<uint>? GetPossiblyCompletedBadgeAppIDs(IDocument badgePage) {
|
||||
if (badgePage == null) {
|
||||
throw new ArgumentNullException(nameof(badgePage));
|
||||
}
|
||||
|
||||
List<IElement> linkElements = badgePage.SelectNodes("//a[@class='badge_craft_button']");
|
||||
|
||||
// We need to also select all badges that we have max level, as those will not display with a craft button
|
||||
// Level 5 is maximum level for card badges according to https://steamcommunity.com/tradingcards/faq
|
||||
linkElements.AddRange(badgePage.SelectNodes("//div[@class='badges_sheet']/div[contains(@class, 'badge_row') and .//div[@class='badge_info_description']/div[contains(text(), 'Level 5')]]/a[@class='badge_row_overlay']"));
|
||||
|
||||
if (linkElements.Count == 0) {
|
||||
return new HashSet<uint>(0);
|
||||
}
|
||||
|
||||
HashSet<uint> result = new(linkElements.Count);
|
||||
|
||||
foreach (string? badgeUri in linkElements.Select(htmlNode => htmlNode.GetAttribute("href"))) {
|
||||
if (string.IsNullOrEmpty(badgeUri)) {
|
||||
ArchiLogger.LogNullError(nameof(badgeUri));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// URIs to foil badges are the same as for normal badges except they end with "?border=1"
|
||||
string appIDText = badgeUri.Split('?', StringSplitOptions.RemoveEmptyEntries)[0].Split('/', StringSplitOptions.RemoveEmptyEntries)[^1];
|
||||
|
||||
if (!uint.TryParse(appIDText, out uint appID) || (appID == 0)) {
|
||||
ArchiLogger.LogNullError(nameof(appID));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
result.Add(appID);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void HandleCallbacks() {
|
||||
TimeSpan timeSpan = TimeSpan.FromMilliseconds(CallbackSleep);
|
||||
|
||||
@@ -2595,6 +2650,18 @@ namespace ArchiSteamFarm {
|
||||
await Commands.HandleMessage(notification.steamid_friend, message).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private void OnInventoryChanged() {
|
||||
Utilities.InBackground(CardsFarmer.OnNewItemsNotification);
|
||||
|
||||
if (BotConfig.BotBehaviour.HasFlag(BotConfig.EBotBehaviour.DismissInventoryNotifications)) {
|
||||
Utilities.InBackground(ArchiWebHandler.MarkInventory);
|
||||
}
|
||||
|
||||
if (BotConfig.CompleteTypesToSend.Count > 0) {
|
||||
Utilities.InBackground(SendCompletedSets);
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnLicenseList(SteamApps.LicenseListCallback callback) {
|
||||
if (callback == null) {
|
||||
throw new ArgumentNullException(nameof(callback));
|
||||
@@ -3130,149 +3197,6 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
private void OnInventoryChanged() {
|
||||
Utilities.InBackground(CardsFarmer.OnNewItemsNotification);
|
||||
|
||||
if (BotConfig.BotBehaviour.HasFlag(BotConfig.EBotBehaviour.DismissInventoryNotifications)) {
|
||||
Utilities.InBackground(ArchiWebHandler.MarkInventory);
|
||||
}
|
||||
|
||||
if (BotConfig.CompleteTypesToSend.Count > 0) {
|
||||
Utilities.InBackground(SendCompletedSets);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<HashSet<uint>?> GetPossiblyCompletedBadgeAppIDs(byte page) {
|
||||
if (page == 0) {
|
||||
throw new ArgumentOutOfRangeException(nameof(page));
|
||||
}
|
||||
|
||||
using IDocument? badgePage = await ArchiWebHandler.GetBadgePage(page).ConfigureAwait(false);
|
||||
|
||||
if (badgePage == null) {
|
||||
ArchiLogger.LogGenericWarning(Strings.WarningCouldNotCheckBadges);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return GetPossiblyCompletedBadgeAppIDs(badgePage);
|
||||
}
|
||||
|
||||
private HashSet<uint>? GetPossiblyCompletedBadgeAppIDs(IDocument badgePage) {
|
||||
if (badgePage == null) {
|
||||
throw new ArgumentNullException(nameof(badgePage));
|
||||
}
|
||||
|
||||
List<IElement> linkElements = badgePage.SelectNodes("//a[@class='badge_craft_button']");
|
||||
|
||||
// We need to also select all badges that we have max level, as those will not display with a craft button
|
||||
// Level 5 is maximum level for card badges according to https://steamcommunity.com/tradingcards/faq
|
||||
linkElements.AddRange(badgePage.SelectNodes("//div[@class='badges_sheet']/div[contains(@class, 'badge_row') and .//div[@class='badge_info_description']/div[contains(text(), 'Level 5')]]/a[@class='badge_row_overlay']"));
|
||||
|
||||
if (linkElements.Count == 0) {
|
||||
return new HashSet<uint>(0);
|
||||
}
|
||||
|
||||
HashSet<uint> result = new(linkElements.Count);
|
||||
|
||||
foreach (string? badgeUri in linkElements.Select(htmlNode => htmlNode.GetAttribute("href"))) {
|
||||
if (string.IsNullOrEmpty(badgeUri)) {
|
||||
ArchiLogger.LogNullError(nameof(badgeUri));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// URIs to foil badges are the same as for normal badges except they end with "?border=1"
|
||||
string appIDText = badgeUri.Split('?', StringSplitOptions.RemoveEmptyEntries)[0].Split('/', StringSplitOptions.RemoveEmptyEntries)[^1];
|
||||
|
||||
if (!uint.TryParse(appIDText, out uint appID) || (appID == 0)) {
|
||||
ArchiLogger.LogNullError(nameof(appID));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
result.Add(appID);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private async Task SendCompletedSets() {
|
||||
lock (SendCompleteTypesSemaphore) {
|
||||
if (SendCompleteTypesScheduled) {
|
||||
return;
|
||||
}
|
||||
|
||||
SendCompleteTypesScheduled = true;
|
||||
}
|
||||
|
||||
await SendCompleteTypesSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
try {
|
||||
using (await Actions.GetTradingLock().ConfigureAwait(false)) {
|
||||
lock (SendCompleteTypesSemaphore) {
|
||||
SendCompleteTypesScheduled = false;
|
||||
}
|
||||
|
||||
HashSet<uint>? appIDs = await GetPossiblyCompletedBadgeAppIDs().ConfigureAwait(false);
|
||||
|
||||
if ((appIDs == null) || (appIDs.Count == 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
HashSet<Steam.Asset> inventory;
|
||||
|
||||
try {
|
||||
inventory = await ArchiWebHandler.GetInventoryAsync()
|
||||
.Where(item => item.Tradable && appIDs.Contains(item.RealAppID) && BotConfig.CompleteTypesToSend.Contains(item.Type))
|
||||
.ToHashSetAsync()
|
||||
.ConfigureAwait(false);
|
||||
} catch (HttpRequestException e) {
|
||||
ArchiLogger.LogGenericWarningException(e);
|
||||
|
||||
return;
|
||||
} catch (Exception e) {
|
||||
ArchiLogger.LogGenericException(e);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (inventory.Count == 0) {
|
||||
ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsEmpty, nameof(inventory)));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), List<uint>> inventorySets = Trading.GetInventorySets(inventory);
|
||||
appIDs.IntersectWith(inventorySets.Where(kv => kv.Value.Count >= MinCardsPerBadge).Select(kv => kv.Key.RealAppID));
|
||||
|
||||
if (appIDs.Count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
Dictionary<uint, byte>? cardCountPerAppID = await LoadCardsPerSet(appIDs).ConfigureAwait(false);
|
||||
|
||||
if ((cardCountPerAppID == null) || (cardCountPerAppID.Count == 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), (uint Sets, byte CardsPerSet)> itemsToTakePerInventorySet = inventorySets.Where(kv => appIDs.Contains(kv.Key.RealAppID)).ToDictionary(kv => kv.Key, kv => (kv.Value[0], cardCountPerAppID[kv.Key.RealAppID]));
|
||||
|
||||
if (itemsToTakePerInventorySet.Values.All(value => value.Sets == 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
HashSet<Steam.Asset> result = GetItemsForFullSets(inventory, itemsToTakePerInventorySet);
|
||||
|
||||
if (result.Count > 0) {
|
||||
await Actions.SendInventory(result).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
SendCompleteTypesSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnVanityURLChangedCallback(ArchiHandler.VanityURLChangedCallback callback) {
|
||||
if (callback == null) {
|
||||
throw new ArgumentNullException(nameof(callback));
|
||||
@@ -3439,6 +3363,82 @@ namespace ArchiSteamFarm {
|
||||
StopPlayingWasBlockedTimer();
|
||||
}
|
||||
|
||||
private async Task SendCompletedSets() {
|
||||
lock (SendCompleteTypesSemaphore) {
|
||||
if (SendCompleteTypesScheduled) {
|
||||
return;
|
||||
}
|
||||
|
||||
SendCompleteTypesScheduled = true;
|
||||
}
|
||||
|
||||
await SendCompleteTypesSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
try {
|
||||
using (await Actions.GetTradingLock().ConfigureAwait(false)) {
|
||||
lock (SendCompleteTypesSemaphore) {
|
||||
SendCompleteTypesScheduled = false;
|
||||
}
|
||||
|
||||
HashSet<uint>? appIDs = await GetPossiblyCompletedBadgeAppIDs().ConfigureAwait(false);
|
||||
|
||||
if ((appIDs == null) || (appIDs.Count == 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
HashSet<Steam.Asset> inventory;
|
||||
|
||||
try {
|
||||
inventory = await ArchiWebHandler.GetInventoryAsync()
|
||||
.Where(item => item.Tradable && appIDs.Contains(item.RealAppID) && BotConfig.CompleteTypesToSend.Contains(item.Type))
|
||||
.ToHashSetAsync()
|
||||
.ConfigureAwait(false);
|
||||
} catch (HttpRequestException e) {
|
||||
ArchiLogger.LogGenericWarningException(e);
|
||||
|
||||
return;
|
||||
} catch (Exception e) {
|
||||
ArchiLogger.LogGenericException(e);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (inventory.Count == 0) {
|
||||
ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsEmpty, nameof(inventory)));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), List<uint>> inventorySets = Trading.GetInventorySets(inventory);
|
||||
appIDs.IntersectWith(inventorySets.Where(kv => kv.Value.Count >= MinCardsPerBadge).Select(kv => kv.Key.RealAppID));
|
||||
|
||||
if (appIDs.Count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
Dictionary<uint, byte>? cardCountPerAppID = await LoadCardsPerSet(appIDs).ConfigureAwait(false);
|
||||
|
||||
if ((cardCountPerAppID == null) || (cardCountPerAppID.Count == 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), (uint Sets, byte CardsPerSet)> itemsToTakePerInventorySet = inventorySets.Where(kv => appIDs.Contains(kv.Key.RealAppID)).ToDictionary(kv => kv.Key, kv => (kv.Value[0], cardCountPerAppID[kv.Key.RealAppID]));
|
||||
|
||||
if (itemsToTakePerInventorySet.Values.All(value => value.Sets == 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
HashSet<Steam.Asset> result = GetItemsForFullSets(inventory, itemsToTakePerInventorySet);
|
||||
|
||||
if (result.Count > 0) {
|
||||
await Actions.SendInventory(result).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
SendCompleteTypesSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private bool ShouldAckChatMessage(ulong steamID) {
|
||||
if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) {
|
||||
throw new ArgumentOutOfRangeException(nameof(steamID));
|
||||
@@ -3511,7 +3511,7 @@ namespace ArchiSteamFarm {
|
||||
byte i = 0;
|
||||
byte[] password = new byte[steamParentalCode.Length];
|
||||
|
||||
foreach (char character in steamParentalCode.TakeWhile(character => (character >= '0') && (character <= '9'))) {
|
||||
foreach (char character in steamParentalCode.TakeWhile(character => character is >= '0' and <= '9')) {
|
||||
password[i++] = (byte) character;
|
||||
}
|
||||
|
||||
|
||||
@@ -48,10 +48,10 @@ namespace ArchiSteamFarm {
|
||||
public const EBotBehaviour DefaultBotBehaviour = EBotBehaviour.None;
|
||||
|
||||
[PublicAPI]
|
||||
public const string DefaultCustomGamePlayedWhileFarming = null;
|
||||
public const string? DefaultCustomGamePlayedWhileFarming = null;
|
||||
|
||||
[PublicAPI]
|
||||
public const string DefaultCustomGamePlayedWhileIdle = null;
|
||||
public const string? DefaultCustomGamePlayedWhileIdle = null;
|
||||
|
||||
[PublicAPI]
|
||||
public const bool DefaultEnabled = false;
|
||||
@@ -87,19 +87,19 @@ namespace ArchiSteamFarm {
|
||||
public const bool DefaultShutdownOnFarmingFinished = false;
|
||||
|
||||
[PublicAPI]
|
||||
public const string DefaultSteamLogin = null;
|
||||
public const string? DefaultSteamLogin = null;
|
||||
|
||||
[PublicAPI]
|
||||
public const ulong DefaultSteamMasterClanID = 0;
|
||||
|
||||
[PublicAPI]
|
||||
public const string DefaultSteamParentalCode = null;
|
||||
public const string? DefaultSteamParentalCode = null;
|
||||
|
||||
[PublicAPI]
|
||||
public const string DefaultSteamPassword = null;
|
||||
public const string? DefaultSteamPassword = null;
|
||||
|
||||
[PublicAPI]
|
||||
public const string DefaultSteamTradeToken = null;
|
||||
public const string? DefaultSteamTradeToken = null;
|
||||
|
||||
[PublicAPI]
|
||||
public const ETradingPreferences DefaultTradingPreferences = ETradingPreferences.None;
|
||||
|
||||
@@ -669,7 +669,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
levelText = levelText[levelStartIndex..levelEndIndex];
|
||||
|
||||
if (!byte.TryParse(levelText, out badgeLevel) || (badgeLevel == 0) || (badgeLevel > 5)) {
|
||||
if (!byte.TryParse(levelText, out badgeLevel) || badgeLevel is 0 or > 5) {
|
||||
Bot.ArchiLogger.LogNullError(nameof(badgeLevel));
|
||||
|
||||
continue;
|
||||
@@ -1041,7 +1041,7 @@ namespace ArchiSteamFarm {
|
||||
Bot.ArchiLogger.LogGenericInfo(Strings.CheckingOtherBadgePages);
|
||||
|
||||
for (byte page = 2; page <= maxPages; page++) {
|
||||
// We need a copy of variable being passed when in for loops, as loop will proceed before our task is launched
|
||||
// ReSharper disable once InlineTemporaryVariable - we need a copy of variable being passed when in for loops, as loop will proceed before our task is launched
|
||||
byte currentPage = page;
|
||||
tasks.Add(CheckPage(currentPage, parsedAppIDs));
|
||||
}
|
||||
|
||||
@@ -160,7 +160,7 @@ namespace ArchiSteamFarm {
|
||||
MarkdownDocument markdownDocument = Markdown.Parse(markdownText);
|
||||
MarkdownDocument result = new();
|
||||
|
||||
foreach (Block block in markdownDocument.SkipWhile(block => block is not HeadingBlock headingBlock || headingBlock.Inline?.FirstChild is not LiteralInline literalInline || !literalInline.Content.ToString().Equals("Changelog", StringComparison.OrdinalIgnoreCase)).Skip(1).TakeWhile(block => block is not ThematicBreakBlock).ToList()) {
|
||||
foreach (Block block in markdownDocument.SkipWhile(block => block is not HeadingBlock { Inline: { FirstChild: LiteralInline literalInline } } || !literalInline.Content.ToString().Equals("Changelog", StringComparison.OrdinalIgnoreCase)).Skip(1).TakeWhile(block => block is not ThematicBreakBlock).ToList()) {
|
||||
// All blocks that we're interested in must be removed from original markdownDocument firstly
|
||||
markdownDocument.Remove(block);
|
||||
result.Add(block);
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace ArchiSteamFarm {
|
||||
public const bool DefaultAutoRestart = true;
|
||||
|
||||
[PublicAPI]
|
||||
public const string DefaultCommandPrefix = "!";
|
||||
public const string? DefaultCommandPrefix = "!";
|
||||
|
||||
[PublicAPI]
|
||||
public const byte DefaultConfirmationsLimiterDelay = 10;
|
||||
@@ -50,7 +50,7 @@ namespace ArchiSteamFarm {
|
||||
public const byte DefaultConnectionTimeout = 90;
|
||||
|
||||
[PublicAPI]
|
||||
public const string DefaultCurrentCulture = null;
|
||||
public const string? DefaultCurrentCulture = null;
|
||||
|
||||
[PublicAPI]
|
||||
public const bool DefaultDebug = false;
|
||||
@@ -74,7 +74,7 @@ namespace ArchiSteamFarm {
|
||||
public const bool DefaultIPC = false;
|
||||
|
||||
[PublicAPI]
|
||||
public const string DefaultIPCPassword = null;
|
||||
public const string? DefaultIPCPassword = null;
|
||||
|
||||
[PublicAPI]
|
||||
public const ArchiCryptoHelper.EHashingMethod DefaultIPCPasswordFormat = ArchiCryptoHelper.EHashingMethod.PlainText;
|
||||
@@ -95,7 +95,7 @@ namespace ArchiSteamFarm {
|
||||
public const bool DefaultStatistics = true;
|
||||
|
||||
[PublicAPI]
|
||||
public const string DefaultSteamMessagePrefix = "/me ";
|
||||
public const string? DefaultSteamMessagePrefix = "/me ";
|
||||
|
||||
[PublicAPI]
|
||||
public const ulong DefaultSteamOwnerID = 0;
|
||||
@@ -113,13 +113,13 @@ namespace ArchiSteamFarm {
|
||||
public const ushort DefaultWebLimiterDelay = 300;
|
||||
|
||||
[PublicAPI]
|
||||
public const string DefaultWebProxyPassword = null;
|
||||
public const string? DefaultWebProxyPassword = null;
|
||||
|
||||
[PublicAPI]
|
||||
public const string DefaultWebProxyText = null;
|
||||
public const string? DefaultWebProxyText = null;
|
||||
|
||||
[PublicAPI]
|
||||
public const string DefaultWebProxyUsername = null;
|
||||
public const string? DefaultWebProxyUsername = null;
|
||||
|
||||
[PublicAPI]
|
||||
public static readonly ImmutableHashSet<uint> DefaultBlacklist = ImmutableHashSet<uint>.Empty;
|
||||
@@ -329,7 +329,7 @@ namespace ArchiSteamFarm {
|
||||
return (false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorConfigPropertyInvalid, nameof(SteamOwnerID), SteamOwnerID));
|
||||
}
|
||||
|
||||
if ((SteamProtocols <= 0) || (SteamProtocols > ProtocolTypes.All)) {
|
||||
if (SteamProtocols is <= 0 or > ProtocolTypes.All) {
|
||||
return (false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorConfigPropertyInvalid, nameof(SteamProtocols), SteamProtocols));
|
||||
}
|
||||
|
||||
|
||||
@@ -123,7 +123,7 @@ namespace ArchiSteamFarm.IPC.Controllers.Api {
|
||||
request.GlobalConfig.WebProxyPassword = ASF.GlobalConfig.WebProxyPassword;
|
||||
}
|
||||
|
||||
if ((ASF.GlobalConfig.AdditionalProperties != null) && (ASF.GlobalConfig.AdditionalProperties.Count > 0)) {
|
||||
if (ASF.GlobalConfig.AdditionalProperties is { Count: > 0 }) {
|
||||
request.GlobalConfig.AdditionalProperties ??= new Dictionary<string, JToken>(ASF.GlobalConfig.AdditionalProperties.Count, ASF.GlobalConfig.AdditionalProperties.Comparer);
|
||||
|
||||
foreach ((string key, JToken value) in ASF.GlobalConfig.AdditionalProperties.Where(property => !request.GlobalConfig.AdditionalProperties.ContainsKey(property.Key))) {
|
||||
|
||||
@@ -30,60 +30,15 @@ using ArchiSteamFarm.Localization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace ArchiSteamFarm.IPC.Controllers.Api {
|
||||
[Route("Api/WWW")]
|
||||
public sealed class WWWController : ArchiController {
|
||||
/// <summary>
|
||||
/// Fetches history of specific GitHub page from ASF project.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is internal API being utilizied by our ASF-ui IPC frontend. You should not depend on existence of any /Api/WWW endpoints as they can disappear and change anytime.
|
||||
/// </remarks>
|
||||
[HttpGet("GitHub/Wiki/History/{page:required}")]
|
||||
[ProducesResponseType(typeof(GenericResponse<string>), (int) HttpStatusCode.OK)]
|
||||
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.BadRequest)]
|
||||
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.ServiceUnavailable)]
|
||||
public async Task<ActionResult<GenericResponse>> GitHubWikiHistoryGet(string page) {
|
||||
if (string.IsNullOrEmpty(page)) {
|
||||
throw new ArgumentNullException(nameof(page));
|
||||
}
|
||||
|
||||
Dictionary<string, DateTime>? revisions = await GitHub.GetWikiHistory(page).ConfigureAwait(false);
|
||||
|
||||
return revisions != null ? revisions.Count > 0 ? Ok(new GenericResponse<ImmutableDictionary<string, DateTime>>(revisions.ToImmutableDictionary())) : BadRequest(new GenericResponse(false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsInvalid, nameof(page)))) : StatusCode((int) HttpStatusCode.ServiceUnavailable, new GenericResponse(false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorRequestFailedTooManyTimes, WebBrowser.MaxTries)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetches specific GitHub page of ASF project.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is internal API being utilizied by our ASF-ui IPC frontend. You should not depend on existence of any /Api/WWW endpoints as they can disappear and change anytime.
|
||||
/// Specifying revision is optional - when not specified, will fetch latest available. If specified revision is invalid, GitHub will automatically fetch the latest revision as well.
|
||||
/// </remarks>
|
||||
[HttpGet("GitHub/Wiki/Page/{page:required}")]
|
||||
[ProducesResponseType(typeof(GenericResponse<string>), (int) HttpStatusCode.OK)]
|
||||
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.BadRequest)]
|
||||
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.ServiceUnavailable)]
|
||||
public async Task<ActionResult<GenericResponse>> GitHubWikiPageGet(string page, [FromQuery] string? revision = null) {
|
||||
if (string.IsNullOrEmpty(page)) {
|
||||
throw new ArgumentNullException(nameof(page));
|
||||
}
|
||||
|
||||
string? html = await GitHub.GetWikiPage(page, revision).ConfigureAwait(false);
|
||||
|
||||
return html switch {
|
||||
null => StatusCode((int) HttpStatusCode.ServiceUnavailable, new GenericResponse(false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorRequestFailedTooManyTimes, WebBrowser.MaxTries))),
|
||||
"" => BadRequest(new GenericResponse(false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsInvalid, nameof(page)))),
|
||||
_ => Ok(new GenericResponse<string>(html))
|
||||
};
|
||||
}
|
||||
|
||||
[Route("Api/WWW/GitHub")]
|
||||
public sealed class GitHubController : ArchiController {
|
||||
/// <summary>
|
||||
/// Fetches the most recent GitHub release of ASF project.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is internal API being utilizied by our ASF-ui IPC frontend. You should not depend on existence of any /Api/WWW endpoints as they can disappear and change anytime.
|
||||
/// </remarks>
|
||||
[HttpGet("GitHub/Release")]
|
||||
[HttpGet("Release")]
|
||||
[ProducesResponseType(typeof(GenericResponse<GitHubReleaseResponse>), (int) HttpStatusCode.OK)]
|
||||
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.ServiceUnavailable)]
|
||||
public async Task<ActionResult<GenericResponse>> GitHubReleaseGet() {
|
||||
@@ -98,7 +53,7 @@ namespace ArchiSteamFarm.IPC.Controllers.Api {
|
||||
/// <remarks>
|
||||
/// This is internal API being utilizied by our ASF-ui IPC frontend. You should not depend on existence of any /Api/WWW endpoints as they can disappear and change anytime.
|
||||
/// </remarks>
|
||||
[HttpGet("GitHub/Release/{version:required}")]
|
||||
[HttpGet("Release/{version:required}")]
|
||||
[ProducesResponseType(typeof(GenericResponse<GitHubReleaseResponse>), (int) HttpStatusCode.OK)]
|
||||
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.BadRequest)]
|
||||
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.ServiceUnavailable)]
|
||||
@@ -126,5 +81,50 @@ namespace ArchiSteamFarm.IPC.Controllers.Api {
|
||||
|
||||
return releaseResponse != null ? Ok(new GenericResponse<GitHubReleaseResponse>(new GitHubReleaseResponse(releaseResponse))) : StatusCode((int) HttpStatusCode.ServiceUnavailable, new GenericResponse(false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorRequestFailedTooManyTimes, WebBrowser.MaxTries)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetches history of specific GitHub page from ASF project.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is internal API being utilizied by our ASF-ui IPC frontend. You should not depend on existence of any /Api/WWW endpoints as they can disappear and change anytime.
|
||||
/// </remarks>
|
||||
[HttpGet("Wiki/History/{page:required}")]
|
||||
[ProducesResponseType(typeof(GenericResponse<string>), (int) HttpStatusCode.OK)]
|
||||
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.BadRequest)]
|
||||
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.ServiceUnavailable)]
|
||||
public async Task<ActionResult<GenericResponse>> GitHubWikiHistoryGet(string page) {
|
||||
if (string.IsNullOrEmpty(page)) {
|
||||
throw new ArgumentNullException(nameof(page));
|
||||
}
|
||||
|
||||
Dictionary<string, DateTime>? revisions = await GitHub.GetWikiHistory(page).ConfigureAwait(false);
|
||||
|
||||
return revisions != null ? revisions.Count > 0 ? Ok(new GenericResponse<ImmutableDictionary<string, DateTime>>(revisions.ToImmutableDictionary())) : BadRequest(new GenericResponse(false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsInvalid, nameof(page)))) : StatusCode((int) HttpStatusCode.ServiceUnavailable, new GenericResponse(false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorRequestFailedTooManyTimes, WebBrowser.MaxTries)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetches specific GitHub page of ASF project.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is internal API being utilizied by our ASF-ui IPC frontend. You should not depend on existence of any /Api/WWW endpoints as they can disappear and change anytime.
|
||||
/// Specifying revision is optional - when not specified, will fetch latest available. If specified revision is invalid, GitHub will automatically fetch the latest revision as well.
|
||||
/// </remarks>
|
||||
[HttpGet("Wiki/Page/{page:required}")]
|
||||
[ProducesResponseType(typeof(GenericResponse<string>), (int) HttpStatusCode.OK)]
|
||||
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.BadRequest)]
|
||||
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.ServiceUnavailable)]
|
||||
public async Task<ActionResult<GenericResponse>> GitHubWikiPageGet(string page, [FromQuery] string? revision = null) {
|
||||
if (string.IsNullOrEmpty(page)) {
|
||||
throw new ArgumentNullException(nameof(page));
|
||||
}
|
||||
|
||||
string? html = await GitHub.GetWikiPage(page, revision).ConfigureAwait(false);
|
||||
|
||||
return html switch {
|
||||
null => StatusCode((int) HttpStatusCode.ServiceUnavailable, new GenericResponse(false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorRequestFailedTooManyTimes, WebBrowser.MaxTries))),
|
||||
"" => BadRequest(new GenericResponse(false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsInvalid, nameof(page)))),
|
||||
_ => Ok(new GenericResponse<string>(html))
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,7 @@ namespace ArchiSteamFarm.IPC.Integration {
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if ((context.Type == null) || !context.Type.IsEnum) {
|
||||
if (context.Type is not { IsEnum: true }) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -371,7 +371,7 @@ namespace ArchiSteamFarm.NLog {
|
||||
private static void InitConsoleLoggers() {
|
||||
ConsoleLoggingRules.Clear();
|
||||
|
||||
foreach (LoggingRule loggingRule in LogManager.Configuration.LoggingRules.Where(loggingRule => loggingRule.Targets.Any(target => target is ColoredConsoleTarget || target is ConsoleTarget))) {
|
||||
foreach (LoggingRule loggingRule in LogManager.Configuration.LoggingRules.Where(loggingRule => loggingRule.Targets.Any(target => target is ColoredConsoleTarget or ConsoleTarget))) {
|
||||
ConsoleLoggingRules.Add(loggingRule);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ using SteamKit2;
|
||||
|
||||
namespace ArchiSteamFarm.Plugins {
|
||||
internal static class PluginsCore {
|
||||
internal static bool HasCustomPluginsLoaded => ActivePlugins?.Any(plugin => !(plugin is OfficialPlugin officialPlugin) || !officialPlugin.HasSameVersion()) == true;
|
||||
internal static bool HasCustomPluginsLoaded => ActivePlugins?.Any(plugin => plugin is not OfficialPlugin officialPlugin || !officialPlugin.HasSameVersion()) == true;
|
||||
|
||||
[ImportMany]
|
||||
internal static ImmutableHashSet<IPlugin>? ActivePlugins { get; private set; }
|
||||
|
||||
@@ -434,7 +434,7 @@ namespace ArchiSteamFarm {
|
||||
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)) && ((holdDuration.Value == 0) || !(((item.Type == Steam.Asset.EType.FoilTradingCard) || (item.Type == Steam.Asset.EType.TradingCard)) && CardsFarmer.SalesBlacklist.Contains(item.RealAppID)))).ToHashSetAsync().ConfigureAwait(false);
|
||||
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 is Steam.Asset.EType.FoilTradingCard or Steam.Asset.EType.TradingCard && CardsFarmer.SalesBlacklist.Contains(item.RealAppID)))).ToHashSetAsync().ConfigureAwait(false);
|
||||
} catch (HttpRequestException e) {
|
||||
Bot.ArchiLogger.LogGenericWarningException(e);
|
||||
|
||||
|
||||
@@ -620,7 +620,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
// If user has a trade hold, we add extra logic
|
||||
// If trade hold duration exceeds our max, or user asks for cards with short lifespan, reject the trade
|
||||
case > 0 when (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)):
|
||||
case > 0 when (holdDuration.Value > ASF.GlobalConfig.MaxTradeHoldDuration) || tradeOffer.ItemsToGive.Any(item => item.Type is Steam.Asset.EType.FoilTradingCard or Steam.Asset.EType.TradingCard && CardsFarmer.SalesBlacklist.Contains(item.RealAppID)):
|
||||
Bot.ArchiLogger.LogGenericDebug(string.Format(CultureInfo.CurrentCulture, Strings.BotTradeOfferResult, tradeOffer.TradeOfferID, ParseTradeResult.EResult.Rejected, nameof(holdDuration) + " > 0: " + holdDuration.Value));
|
||||
|
||||
return ParseTradeResult.EResult.Rejected;
|
||||
|
||||
@@ -172,10 +172,10 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public static bool IsClientErrorCode(this HttpStatusCode statusCode) => (statusCode >= HttpStatusCode.BadRequest) && (statusCode < HttpStatusCode.InternalServerError);
|
||||
public static bool IsClientErrorCode(this HttpStatusCode statusCode) => statusCode is >= HttpStatusCode.BadRequest and < HttpStatusCode.InternalServerError;
|
||||
|
||||
[PublicAPI]
|
||||
public static bool IsServerErrorCode(this HttpStatusCode statusCode) => (statusCode >= HttpStatusCode.InternalServerError) && (statusCode < (HttpStatusCode) 600);
|
||||
public static bool IsServerErrorCode(this HttpStatusCode statusCode) => statusCode is >= HttpStatusCode.InternalServerError and < (HttpStatusCode) 600;
|
||||
|
||||
[PublicAPI]
|
||||
public static bool IsValidCdKey(string key) {
|
||||
|
||||
@@ -805,14 +805,6 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
if (response == null) {
|
||||
if (Debugging.IsUserDebugging) {
|
||||
ArchiLogger.LogGenericDebug("null <- " + httpMethod + " " + requestUri);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Debugging.IsUserDebugging) {
|
||||
ArchiLogger.LogGenericDebug(response.StatusCode + " <- " + httpMethod + " " + requestUri);
|
||||
}
|
||||
@@ -822,7 +814,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
// WARNING: We still have not disposed response by now, make sure to dispose it ASAP if we're not returning it!
|
||||
if ((response.StatusCode >= HttpStatusCode.Ambiguous) && (response.StatusCode < HttpStatusCode.BadRequest) && (maxRedirections > 0)) {
|
||||
if (response.StatusCode is >= HttpStatusCode.Ambiguous and < HttpStatusCode.BadRequest && (maxRedirections > 0)) {
|
||||
Uri? redirectUri = response.Headers.Location;
|
||||
|
||||
if (redirectUri == null) {
|
||||
|
||||
Reference in New Issue
Block a user