2018-12-16 01:08:09 +03:00
// _ _ _ ____ _ _____
2017-11-18 17:27:06 +01:00
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
2019-01-14 19:11:17 +01:00
// |
2019-01-02 16:32:53 +01:00
// Copyright 2015-2019 Łukasz "JustArchi" Domeradzki
2018-07-27 04:52:14 +02:00
// Contact: JustArchi@JustArchi.net
2019-01-14 19:11:17 +01:00
// |
2018-07-27 04:52:14 +02:00
// 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
2019-01-14 19:11:17 +01:00
// |
2018-07-27 04:52:14 +02:00
// http://www.apache.org/licenses/LICENSE-2.0
2019-01-14 19:11:17 +01:00
// |
2018-07-27 04:52:14 +02:00
// 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.
2016-11-06 12:06:02 +01:00
2015-10-25 06:16:50 +01:00
using System ;
using System.Collections.Generic ;
2018-12-14 16:07:46 +01:00
using System.Collections.Immutable ;
Enhance InventoryLimiterDelay
Gifts/Login limiter is actually working decent, because response is nearly instant and fast enough to not worry about it in long-run.
With inventory things are entirely different, as inventory fetching might take even a very long time, and while fetching one inventory, we might already run out of our delay and start fetching another one.
This is not a big functionality-wise, as it's nothing new for ASF to parse multiple inventories concurrently, but Steam Community actually counts number of requests, and our inventory function might ask for multiple pages during execution, which could quickly lead to a situation of 10+ ongoing inventory requests being sent concurrently for too many accounts at once, as we can't predict not only how long the request will be handled, but also how many sub-requests we will do across one.
This means that for optimal performance in terms of rate-limiting, we must limit ASF to one inventory request at a time, with mandatory InventoryLimiterDelay before asking for another one.
This can degrade performance of previously fast !loot requests on multiple accounts at once (especially with bigger inventories), but it will also decrease significantly a chance of getting rate-limited and requests failing.
2017-04-08 04:47:38 +02:00
using System.Diagnostics.CodeAnalysis ;
2016-05-30 01:57:06 +02:00
using System.Linq ;
2015-10-25 06:16:50 +01:00
using System.Net ;
2019-01-02 18:09:07 +01:00
using System.Net.Http ;
2015-10-25 06:16:50 +01:00
using System.Text ;
2016-11-24 07:32:16 +01:00
using System.Threading ;
2015-10-25 06:16:50 +01:00
using System.Threading.Tasks ;
2016-03-15 04:20:28 +01:00
using System.Xml ;
2018-12-12 22:19:52 +01:00
using ArchiSteamFarm.Helpers ;
2017-12-14 08:23:17 +01:00
using ArchiSteamFarm.Json ;
2017-01-06 13:20:36 +01:00
using ArchiSteamFarm.Localization ;
2016-11-24 07:32:16 +01:00
using HtmlAgilityPack ;
2019-01-10 22:33:07 +01:00
using JetBrains.Annotations ;
2016-11-24 07:32:16 +01:00
using Newtonsoft.Json ;
using Newtonsoft.Json.Linq ;
using SteamKit2 ;
2016-12-04 05:50:11 +01:00
using Formatting = Newtonsoft . Json . Formatting ;
2015-10-25 06:16:50 +01:00
namespace ArchiSteamFarm {
2019-01-10 22:33:07 +01:00
public sealed class ArchiWebHandler : IDisposable {
[PublicAPI]
public const string SteamCommunityURL = "https://" + SteamCommunityHost ;
[PublicAPI]
public const string SteamStoreURL = "https://" + SteamStoreHost ;
2017-01-06 13:20:36 +01:00
private const string IEconService = "IEconService" ;
private const string IPlayerService = "IPlayerService" ;
2018-07-04 19:13:51 +02:00
private const string ISteamApps = "ISteamApps" ;
2017-01-06 13:20:36 +01:00
private const string ISteamUserAuth = "ISteamUserAuth" ;
private const string ITwoFactorService = "ITwoFactorService" ;
Implement ETradingPreferences.MatchActively
This will probably need a lot more tests, tweaking and bugfixing, but basic logic is:
- MatchActively added to TradingPreferences with value of 16
- User must also use SteamTradeMatcher, can't use MatchEverything
- User must have statistics enabled and be eligible for being listed (no requirement of having 100 items minimum)
Once all requirements are passed, statistics module will communicate with the listing and fetch match everything bots:
- The matching will start in 1h since ASF start and will repeat every day (right now it starts in 1 minute to aid debugging).
- Each matching is composed of up to 10 rounds maximum.
- In each round ASF will fetch our inventory and inventory of listed bots in order to find MatchableTypes items to be matched. If match is found, offer is being sent and confirmed automatically.
- Each set (composition of item type + appID it's from) can be matched in a single round only once, this is to minimize "items no longer available" as much as possible and also avoid a need to wait for each bot to react before sending all trades.
- Round ends when we try to match a total of 20 bots, or we hit no items to match in consecutive 10 tries with 10 different bots.
- If last round resulted in at least a single trade being sent, next round starts within 5 minutes since last one, otherwise matching ends and repeats the next day.
We'll see how it works in practice, expect a lot of follow-up commits, unless I won't have anything to fix or improve.
2018-11-29 18:35:58 +01:00
private const ushort MaxItemsInSingleInventoryRequest = 5000 ;
2018-11-06 21:41:59 +01:00
private const byte MinSessionValidityInSeconds = GlobalConfig . DefaultConnectionTimeout / 6 ;
2016-12-25 05:53:47 +01:00
private const string SteamCommunityHost = "steamcommunity.com" ;
Remove ForceHttp option
Initially I wanted to make it configurable to choose either HTTPS (preferred), or HTTP, depending on user choice.
I strongly believed that it WAS possible without much headache, and solve many older Mono issues without any strong code drawbacks.
However, Volvo proved me wrong yet again, as it seems that using HTTP just like that for accepting a trade makes it impossible, and that's ONLY because we're using HTTP and not HTTPS, even if all other data, including referer, post and request, looks exactly the same.
It's quite sad that I must remove this option, but I literally discovered that switching this to true makes accepting trades impossible, and that is beyond the point I can accept, as user could switch this to true when he doesn't need it, and limit program functionality without even knowing that this is the cause.
Everybody using up-to-date Mono should have no issues using HTTPS, even legacy TLS 1.0, so hopefully this won't hurt that marginal percent of users that had this set to true in the past. It was mentioned in the wiki that this option might disappear later on, and this is the moment when it doesn't only should, but MUST, disappear... :cry:
2016-12-23 19:04:36 +01:00
private const string SteamStoreHost = "store.steampowered.com" ;
2016-03-13 20:19:52 +01:00
2017-08-01 12:41:57 +02:00
private static readonly SemaphoreSlim InventorySemaphore = new SemaphoreSlim ( 1 , 1 ) ;
Enhance InventoryLimiterDelay
Gifts/Login limiter is actually working decent, because response is nearly instant and fast enough to not worry about it in long-run.
With inventory things are entirely different, as inventory fetching might take even a very long time, and while fetching one inventory, we might already run out of our delay and start fetching another one.
This is not a big functionality-wise, as it's nothing new for ASF to parse multiple inventories concurrently, but Steam Community actually counts number of requests, and our inventory function might ask for multiple pages during execution, which could quickly lead to a situation of 10+ ongoing inventory requests being sent concurrently for too many accounts at once, as we can't predict not only how long the request will be handled, but also how many sub-requests we will do across one.
This means that for optimal performance in terms of rate-limiting, we must limit ASF to one inventory request at a time, with mandatory InventoryLimiterDelay before asking for another one.
This can degrade performance of previously fast !loot requests on multiple accounts at once (especially with bigger inventories), but it will also decrease significantly a chance of getting rate-limited and requests failing.
2017-04-08 04:47:38 +02:00
2019-01-10 22:33:07 +01:00
private static readonly ImmutableDictionary < string , ( SemaphoreSlim RateLimitingSemaphore , SemaphoreSlim OpenConnectionsSemaphore ) > WebLimitingSemaphores = new Dictionary < string , ( SemaphoreSlim RateLimitingSemaphore , SemaphoreSlim OpenConnectionsSemaphore ) > ( 4 ) {
{ nameof ( ArchiWebHandler ) , ( new SemaphoreSlim ( 1 , 1 ) , new SemaphoreSlim ( WebBrowser . MaxConnections , WebBrowser . MaxConnections ) ) } ,
2018-04-13 09:49:54 +02:00
{ SteamCommunityURL , ( new SemaphoreSlim ( 1 , 1 ) , new SemaphoreSlim ( WebBrowser . MaxConnections , WebBrowser . MaxConnections ) ) } ,
{ SteamStoreURL , ( new SemaphoreSlim ( 1 , 1 ) , new SemaphoreSlim ( WebBrowser . MaxConnections , WebBrowser . MaxConnections ) ) } ,
{ WebAPI . DefaultBaseAddress . Host , ( new SemaphoreSlim ( 1 , 1 ) , new SemaphoreSlim ( WebBrowser . MaxConnections , WebBrowser . MaxConnections ) ) }
2018-12-14 16:07:46 +01:00
} . ToImmutableDictionary ( ) ;
2018-04-13 09:17:27 +02:00
2019-01-10 22:33:07 +01:00
[PublicAPI]
public readonly ArchiCacheable < string > CachedApiKey ;
[PublicAPI]
public readonly WebBrowser WebBrowser ;
2015-10-25 06:16:50 +01:00
private readonly Bot Bot ;
2018-12-16 01:08:09 +03:00
private readonly ArchiCacheable < bool > CachedPublicInventory ;
2017-08-01 12:41:57 +02:00
private readonly SemaphoreSlim SessionSemaphore = new SemaphoreSlim ( 1 , 1 ) ;
2016-03-27 23:07:00 +02:00
2018-11-06 21:41:59 +01:00
private DateTime LastSessionCheck ;
2018-03-05 19:01:35 +01:00
private DateTime LastSessionRefresh ;
Fix latest Steam notifications fuckup
Initially the issue was observed in #697, but that itself wasn't exactly what was fixed here, as multiple evaluation of the same trade is still wanted scenario.
The real issue was reported in http://steamcommunity.com/groups/ascfarm/discussions/1/2425614539578192287/
In a huge TL;DR, Steam is now sending trades notification each time something fetches current trade offers, be it ASF, the user, or some other script.
This will lead to possible ASF trade loop, as we'll get wanted notification about new trades, fetch them, leave some trades untouched, get new notification about trades and so on.
Initially I wanted to fix this in dirty way by just ignoring any extra notifications that happened since API call until 5 extra seconds after we were done with entire parsing, but I found much better solution - Steam actually includes extra info about amount of trades/items in notification (makes sense, since Steam client displays that info too). We can make use of that info and simply ignore any extra notification that results in same or smaller count.
Thanks to that we didn't only add a decent workaround for this recent Steam fuckup, but we also improved internal ASF code that will no longer schedule extra parsing if we accepted/rejected only some of the trades, making me happy with the actual solution.
2017-11-18 17:20:24 +01:00
private bool MarkingInventoryScheduled ;
2016-11-24 07:32:16 +01:00
private ulong SteamID ;
2017-07-24 10:39:51 +02:00
private string VanityURL ;
2015-12-18 15:50:10 +01:00
2019-01-10 23:44:32 +01:00
internal ArchiWebHandler ( [ NotNull ] Bot bot ) {
2017-03-14 08:20:29 -03:00
Bot = bot ? ? throw new ArgumentNullException ( nameof ( bot ) ) ;
2018-12-12 22:19:52 +01:00
2018-12-16 01:08:09 +03:00
CachedApiKey = new ArchiCacheable < string > ( ResolveApiKey , TimeSpan . FromHours ( 1 ) ) ;
CachedPublicInventory = new ArchiCacheable < bool > ( ResolvePublicInventory , TimeSpan . FromHours ( 1 ) ) ;
2019-01-14 21:50:23 +01:00
WebBrowser = new WebBrowser ( bot . ArchiLogger , ASF . GlobalConfig . WebProxy ) ;
2016-03-06 23:28:56 +01:00
}
2017-01-11 15:41:02 +01:00
public void Dispose ( ) {
2018-12-12 22:19:52 +01:00
CachedApiKey . Dispose ( ) ;
CachedPublicInventory . Dispose ( ) ;
2017-01-11 15:41:02 +01:00
SessionSemaphore . Dispose ( ) ;
2017-08-04 19:26:37 +02:00
WebBrowser . Dispose ( ) ;
2017-01-11 15:41:02 +01:00
}
2016-11-24 07:32:16 +01:00
2019-01-10 23:44:32 +01:00
[ItemCanBeNull]
2019-01-10 22:33:07 +01:00
[PublicAPI]
public async Task < string > GetAbsoluteProfileURL ( bool waitForInitialization = true ) {
if ( waitForInitialization & & ( SteamID = = 0 ) ) {
2019-01-14 21:50:23 +01:00
for ( byte i = 0 ; ( i < ASF . GlobalConfig . ConnectionTimeout ) & & ( SteamID = = 0 ) & & Bot . IsConnectedAndLoggedOn ; i + + ) {
2019-01-10 22:33:07 +01:00
await Task . Delay ( 1000 ) . ConfigureAwait ( false ) ;
}
2018-08-20 04:33:09 +03:00
2019-01-10 22:33:07 +01:00
if ( SteamID = = 0 ) {
Bot . ArchiLogger . LogGenericWarning ( Strings . WarningFailed ) ;
2018-08-20 04:33:09 +03:00
2019-01-10 22:33:07 +01:00
return null ;
}
}
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return string . IsNullOrEmpty ( VanityURL ) ? "/profiles/" + SteamID : "/id/" + VanityURL ;
2018-08-20 04:33:09 +03:00
}
2019-01-17 16:48:38 +01:00
[ItemCanBeNull]
[PublicAPI]
[SuppressMessage("ReSharper", "FunctionComplexityOverflow")]
public async Task < HashSet < Steam . Asset > > GetInventory ( ulong steamID = 0 , uint appID = Steam . Asset . SteamAppID , uint contextID = Steam . Asset . SteamCommunityContextID , bool? tradable = null , IReadOnlyCollection < uint > wantedRealAppIDs = null , IReadOnlyCollection < Steam . Asset . EType > wantedTypes = null , IReadOnlyCollection < ( uint AppID , Steam . Asset . EType Type ) > wantedSets = null , IReadOnlyCollection < ( uint AppID , Steam . Asset . EType Type ) > skippedSets = null ) {
if ( ( appID = = 0 ) | | ( contextID = = 0 ) ) {
Bot . ArchiLogger . LogNullError ( nameof ( appID ) + " || " + nameof ( contextID ) ) ;
return null ;
}
if ( steamID = = 0 ) {
if ( SteamID = = 0 ) {
for ( byte i = 0 ; ( i < ASF . GlobalConfig . ConnectionTimeout ) & & ( SteamID = = 0 ) & & Bot . IsConnectedAndLoggedOn ; i + + ) {
await Task . Delay ( 1000 ) . ConfigureAwait ( false ) ;
}
if ( SteamID = = 0 ) {
Bot . ArchiLogger . LogGenericWarning ( Strings . WarningFailed ) ;
return null ;
}
}
steamID = SteamID ;
}
string request = "/inventory/" + steamID + "/" + appID + "/" + contextID + "?count=" + MaxItemsInSingleInventoryRequest + "&l=english" ;
ulong startAssetID = 0 ;
HashSet < Steam . Asset > result = new HashSet < Steam . Asset > ( ) ;
while ( true ) {
await InventorySemaphore . WaitAsync ( ) . ConfigureAwait ( false ) ;
try {
Steam . InventoryResponse response = await UrlGetToJsonObjectWithSession < Steam . InventoryResponse > ( SteamCommunityURL , request + ( startAssetID > 0 ? "&start_assetid=" + startAssetID : "" ) ) . ConfigureAwait ( false ) ;
if ( response = = null ) {
return null ;
}
if ( ! response . Success ) {
Bot . ArchiLogger . LogGenericWarning ( ! string . IsNullOrEmpty ( response . Error ) ? string . Format ( Strings . WarningFailedWithError , response . Error ) : Strings . WarningFailed ) ;
return null ;
}
if ( response . TotalInventoryCount = = 0 ) {
// Empty inventory
return result ;
}
if ( ( response . Assets = = null ) | | ( response . Assets . Count = = 0 ) | | ( response . Descriptions = = null ) | | ( response . Descriptions . Count = = 0 ) ) {
Bot . ArchiLogger . LogNullError ( nameof ( response . Assets ) + " || " + nameof ( response . Descriptions ) ) ;
return null ;
}
Dictionary < ulong , ( bool Marketable , bool Tradable , uint RealAppID , Steam . Asset . EType Type ) > descriptions = new Dictionary < ulong , ( bool Marketable , bool Tradable , uint RealAppID , Steam . Asset . EType Type ) > ( ) ;
foreach ( Steam . InventoryResponse . Description description in response . Descriptions . Where ( description = > description ! = null ) ) {
if ( description . ClassID = = 0 ) {
Bot . ArchiLogger . LogNullError ( nameof ( description . ClassID ) ) ;
return null ;
}
if ( descriptions . ContainsKey ( description . ClassID ) ) {
continue ;
}
uint realAppID = 0 ;
Steam . Asset . EType type = Steam . Asset . EType . Unknown ;
if ( appID = = Steam . Asset . SteamAppID ) {
if ( ! string . IsNullOrEmpty ( description . MarketHashName ) ) {
realAppID = GetAppIDFromMarketHashName ( description . MarketHashName ) ;
}
if ( ! string . IsNullOrEmpty ( description . Type ) ) {
type = GetItemType ( description . Type ) ;
}
}
descriptions [ description . ClassID ] = ( description . Marketable , description . Tradable , realAppID , type ) ;
}
foreach ( Steam . Asset asset in response . Assets . Where ( asset = > asset ! = null ) ) {
if ( descriptions . TryGetValue ( asset . ClassID , out ( bool Marketable , bool Tradable , uint RealAppID , Steam . Asset . EType Type ) description ) ) {
if ( ( tradable . HasValue & & ( description . Tradable ! = tradable . Value ) ) | | ( wantedRealAppIDs ? . Contains ( description . RealAppID ) = = false ) | | ( wantedSets ? . Contains ( ( description . RealAppID , description . Type ) ) = = false ) | | ( wantedTypes ? . Contains ( description . Type ) = = false ) | | ( skippedSets ? . Contains ( ( description . RealAppID , description . Type ) ) = = true ) ) {
continue ;
}
asset . Marketable = description . Marketable ;
asset . Tradable = description . Tradable ;
asset . RealAppID = description . RealAppID ;
asset . Type = description . Type ;
} else if ( tradable . HasValue | | ( wantedRealAppIDs ! = null ) | | ( wantedSets ! = null ) | | ( wantedTypes ! = null ) | | ( skippedSets ! = null ) ) {
continue ;
}
result . Add ( asset ) ;
}
if ( ! response . MoreItems ) {
return result ;
}
if ( response . LastAssetID = = 0 ) {
Bot . ArchiLogger . LogNullError ( nameof ( response . LastAssetID ) ) ;
return null ;
}
startAssetID = response . LastAssetID ;
} finally {
if ( ASF . GlobalConfig . InventoryLimiterDelay = = 0 ) {
InventorySemaphore . Release ( ) ;
} else {
Utilities . InBackground (
async ( ) = > {
await Task . Delay ( ASF . GlobalConfig . InventoryLimiterDelay * 1000 ) . ConfigureAwait ( false ) ;
InventorySemaphore . Release ( ) ;
}
) ;
}
}
}
}
2019-01-10 22:33:07 +01:00
[PublicAPI]
public async Task < HtmlDocument > UrlGetToHtmlDocumentWithSession ( string host , string request , bool checkSessionPreemptively = true , byte maxTries = WebBrowser . MaxTries ) {
if ( string . IsNullOrEmpty ( host ) | | string . IsNullOrEmpty ( request ) ) {
Bot . ArchiLogger . LogNullError ( nameof ( host ) + " || " + nameof ( request ) ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return null ;
2016-04-21 00:51:22 +02:00
}
2019-01-10 22:33:07 +01:00
if ( maxTries = = 0 ) {
Bot . ArchiLogger . LogGenericWarning ( string . Format ( Strings . ErrorRequestFailedTooManyTimes , WebBrowser . MaxTries ) ) ;
Bot . ArchiLogger . LogGenericDebug ( string . Format ( Strings . ErrorFailingRequest , host + request ) ) ;
2016-11-24 07:32:16 +01:00
2019-01-10 22:33:07 +01:00
return null ;
}
2016-11-24 07:32:16 +01:00
2019-01-10 22:33:07 +01:00
if ( checkSessionPreemptively ) {
// Check session preemptively as this request might not get redirected to expiration
bool? sessionExpired = await IsSessionExpired ( ) . ConfigureAwait ( false ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
if ( sessionExpired . GetValueOrDefault ( true ) ) {
if ( await RefreshSession ( ) . ConfigureAwait ( false ) ) {
return await UrlGetToHtmlDocumentWithSession ( host , request , true , - - maxTries ) . ConfigureAwait ( false ) ;
}
2016-04-21 00:51:22 +02:00
2019-01-10 22:33:07 +01:00
Bot . ArchiLogger . LogGenericWarning ( Strings . WarningFailed ) ;
Bot . ArchiLogger . LogGenericDebug ( string . Format ( Strings . ErrorFailingRequest , host + request ) ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return null ;
}
} else {
// If session refresh is already in progress, just wait for it
await SessionSemaphore . WaitAsync ( ) . ConfigureAwait ( false ) ;
SessionSemaphore . Release ( ) ;
2016-04-21 00:51:22 +02:00
}
2019-01-10 22:33:07 +01:00
if ( SteamID = = 0 ) {
2019-01-14 21:50:23 +01:00
for ( byte i = 0 ; ( i < ASF . GlobalConfig . ConnectionTimeout ) & & ( SteamID = = 0 ) & & Bot . IsConnectedAndLoggedOn ; i + + ) {
2019-01-10 22:33:07 +01:00
await Task . Delay ( 1000 ) . ConfigureAwait ( false ) ;
}
2016-11-24 07:32:16 +01:00
2019-01-10 22:33:07 +01:00
if ( SteamID = = 0 ) {
Bot . ArchiLogger . LogGenericWarning ( Strings . WarningFailed ) ;
Bot . ArchiLogger . LogGenericDebug ( string . Format ( Strings . ErrorFailingRequest , host + request ) ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return null ;
}
2018-05-20 14:42:48 +02:00
}
2019-01-10 22:33:07 +01:00
WebBrowser . HtmlDocumentResponse response = await WebLimitRequest ( host , async ( ) = > await WebBrowser . UrlGetToHtmlDocument ( host + request ) . ConfigureAwait ( false ) ) . ConfigureAwait ( false ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
if ( response = = null ) {
return null ;
2018-05-20 14:42:48 +02:00
}
2019-01-10 22:33:07 +01:00
if ( IsSessionExpiredUri ( response . FinalUri ) ) {
if ( await RefreshSession ( ) . ConfigureAwait ( false ) ) {
return await UrlGetToHtmlDocumentWithSession ( host , request , checkSessionPreemptively , - - maxTries ) . ConfigureAwait ( false ) ;
}
2018-05-19 20:52:14 +03:00
2019-01-10 22:33:07 +01:00
Bot . ArchiLogger . LogGenericWarning ( Strings . WarningFailed ) ;
Bot . ArchiLogger . LogGenericDebug ( string . Format ( Strings . ErrorFailingRequest , host + request ) ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return null ;
2018-05-19 20:52:14 +03:00
}
2019-01-10 22:33:07 +01:00
// Under special brain-damaged circumstances, Steam might just return our own profile as a response to the request, for absolutely no reason whatsoever - just try again in this case
if ( await IsProfileUri ( response . FinalUri ) . ConfigureAwait ( false ) ) {
Bot . ArchiLogger . LogGenericDebug ( string . Format ( Strings . WarningWorkaroundTriggered , nameof ( IsProfileUri ) ) ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return await UrlGetToHtmlDocumentWithSession ( host , request , checkSessionPreemptively , - - maxTries ) . ConfigureAwait ( false ) ;
2018-05-19 20:52:14 +03:00
}
2019-01-10 22:33:07 +01:00
return response . Content ;
2018-05-19 20:52:14 +03:00
}
2019-01-10 22:33:07 +01:00
[PublicAPI]
public async Task < T > UrlGetToJsonObjectWithSession < T > ( string host , string request , bool checkSessionPreemptively = true , byte maxTries = WebBrowser . MaxTries ) where T : class {
if ( string . IsNullOrEmpty ( host ) | | string . IsNullOrEmpty ( request ) ) {
Bot . ArchiLogger . LogNullError ( nameof ( host ) + " || " + nameof ( request ) ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return default ;
Cleanup after #794
@vital7
Steam.cs:
- Add missing constructor for json deserialization
- Change name into UserPrivacy since we use it for both request and response, no longer response only
- Move ECommentPermission one level above, it's not internal member of PrivacySettings
- Make UserPrivacy constructor accept PrivacySettings, since PrivacySettings is internal and not private.
- Make ECommentPermission underlying type of byte
ArchiWebHandler.cs:
- Put function in proper place alphabetically
- Cast CommentPermission to new underlying type of byte
- Remove mapping, AWH should not be in charge of correcting a caller, unless that caller can't be corrected earlier (e.g. direct Steam response). Since Bot is in charge of calling AWH, Bot should do the correction, not AWH.
Bot.cs:
- Change command to !privacy bot n,n,n,n,n,n, this makes it possible for mixing it with enum members (such as !privacy bot public,private,friendsonly,public,public), which would be preferred way of execution instead of cryptic numbers.
- Make appropriate mapping between general and comments, since userspace is in charge of that.
- Add comments and correct creating UserPrivacy object.
- Make the default option private and let user skip extra options if he wants to edit e.g. only profile to public.
Apart from that, general error handling and a lot of other misc fixed, including renaming NonZeroResponse to NumberResponse, which makes more sense.
2018-05-19 20:50:26 +02:00
}
2019-01-10 22:33:07 +01:00
if ( maxTries = = 0 ) {
Bot . ArchiLogger . LogGenericWarning ( string . Format ( Strings . ErrorRequestFailedTooManyTimes , WebBrowser . MaxTries ) ) ;
Bot . ArchiLogger . LogGenericDebug ( string . Format ( Strings . ErrorFailingRequest , host + request ) ) ;
Cleanup after #794
@vital7
Steam.cs:
- Add missing constructor for json deserialization
- Change name into UserPrivacy since we use it for both request and response, no longer response only
- Move ECommentPermission one level above, it's not internal member of PrivacySettings
- Make UserPrivacy constructor accept PrivacySettings, since PrivacySettings is internal and not private.
- Make ECommentPermission underlying type of byte
ArchiWebHandler.cs:
- Put function in proper place alphabetically
- Cast CommentPermission to new underlying type of byte
- Remove mapping, AWH should not be in charge of correcting a caller, unless that caller can't be corrected earlier (e.g. direct Steam response). Since Bot is in charge of calling AWH, Bot should do the correction, not AWH.
Bot.cs:
- Change command to !privacy bot n,n,n,n,n,n, this makes it possible for mixing it with enum members (such as !privacy bot public,private,friendsonly,public,public), which would be preferred way of execution instead of cryptic numbers.
- Make appropriate mapping between general and comments, since userspace is in charge of that.
- Add comments and correct creating UserPrivacy object.
- Make the default option private and let user skip extra options if he wants to edit e.g. only profile to public.
Apart from that, general error handling and a lot of other misc fixed, including renaming NonZeroResponse to NumberResponse, which makes more sense.
2018-05-19 20:50:26 +02:00
2019-01-10 22:33:07 +01:00
return default ;
}
Cleanup after #794
@vital7
Steam.cs:
- Add missing constructor for json deserialization
- Change name into UserPrivacy since we use it for both request and response, no longer response only
- Move ECommentPermission one level above, it's not internal member of PrivacySettings
- Make UserPrivacy constructor accept PrivacySettings, since PrivacySettings is internal and not private.
- Make ECommentPermission underlying type of byte
ArchiWebHandler.cs:
- Put function in proper place alphabetically
- Cast CommentPermission to new underlying type of byte
- Remove mapping, AWH should not be in charge of correcting a caller, unless that caller can't be corrected earlier (e.g. direct Steam response). Since Bot is in charge of calling AWH, Bot should do the correction, not AWH.
Bot.cs:
- Change command to !privacy bot n,n,n,n,n,n, this makes it possible for mixing it with enum members (such as !privacy bot public,private,friendsonly,public,public), which would be preferred way of execution instead of cryptic numbers.
- Make appropriate mapping between general and comments, since userspace is in charge of that.
- Add comments and correct creating UserPrivacy object.
- Make the default option private and let user skip extra options if he wants to edit e.g. only profile to public.
Apart from that, general error handling and a lot of other misc fixed, including renaming NonZeroResponse to NumberResponse, which makes more sense.
2018-05-19 20:50:26 +02:00
2019-01-10 22:33:07 +01:00
if ( checkSessionPreemptively ) {
// Check session preemptively as this request might not get redirected to expiration
bool? sessionExpired = await IsSessionExpired ( ) . ConfigureAwait ( false ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
if ( sessionExpired . GetValueOrDefault ( true ) ) {
if ( await RefreshSession ( ) . ConfigureAwait ( false ) ) {
return await UrlGetToJsonObjectWithSession < T > ( host , request , true , - - maxTries ) . ConfigureAwait ( false ) ;
}
2017-01-11 15:22:00 +01:00
2019-01-10 22:33:07 +01:00
Bot . ArchiLogger . LogGenericWarning ( Strings . WarningFailed ) ;
Bot . ArchiLogger . LogGenericDebug ( string . Format ( Strings . ErrorFailingRequest , host + request ) ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return null ;
}
} else {
// If session refresh is already in progress, just wait for it
await SessionSemaphore . WaitAsync ( ) . ConfigureAwait ( false ) ;
SessionSemaphore . Release ( ) ;
2016-11-24 07:32:16 +01:00
}
2019-01-10 22:33:07 +01:00
if ( SteamID = = 0 ) {
2019-01-14 21:50:23 +01:00
for ( byte i = 0 ; ( i < ASF . GlobalConfig . ConnectionTimeout ) & & ( SteamID = = 0 ) & & Bot . IsConnectedAndLoggedOn ; i + + ) {
2019-01-10 22:33:07 +01:00
await Task . Delay ( 1000 ) . ConfigureAwait ( false ) ;
}
2017-04-05 17:01:18 +02:00
2019-01-10 22:33:07 +01:00
if ( SteamID = = 0 ) {
Bot . ArchiLogger . LogGenericWarning ( Strings . WarningFailed ) ;
Bot . ArchiLogger . LogGenericDebug ( string . Format ( Strings . ErrorFailingRequest , host + request ) ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return default ;
2017-09-09 20:24:57 +02:00
}
2016-11-24 07:32:16 +01:00
}
2016-05-13 06:32:42 +02:00
2019-01-10 22:33:07 +01:00
WebBrowser . ObjectResponse < T > response = await WebLimitRequest ( host , async ( ) = > await WebBrowser . UrlGetToJsonObject < T > ( host + request ) . ConfigureAwait ( false ) ) . ConfigureAwait ( false ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
if ( response = = null ) {
return default ;
2016-04-21 00:51:22 +02:00
}
2018-03-09 15:43:25 +01:00
2019-01-10 22:33:07 +01:00
if ( IsSessionExpiredUri ( response . FinalUri ) ) {
if ( await RefreshSession ( ) . ConfigureAwait ( false ) ) {
return await UrlGetToJsonObjectWithSession < T > ( host , request , checkSessionPreemptively , - - maxTries ) . ConfigureAwait ( false ) ;
}
2016-04-21 00:51:22 +02:00
2019-01-10 22:33:07 +01:00
Bot . ArchiLogger . LogGenericWarning ( Strings . WarningFailed ) ;
Bot . ArchiLogger . LogGenericDebug ( string . Format ( Strings . ErrorFailingRequest , host + request ) ) ;
2019-01-02 18:09:07 +01:00
2019-01-10 22:33:07 +01:00
return null ;
}
2016-12-23 03:01:37 +01:00
2019-01-10 22:33:07 +01:00
// Under special brain-damaged circumstances, Steam might just return our own profile as a response to the request, for absolutely no reason whatsoever - just try again in this case
if ( await IsProfileUri ( response . FinalUri ) . ConfigureAwait ( false ) ) {
Bot . ArchiLogger . LogGenericDebug ( string . Format ( Strings . WarningWorkaroundTriggered , nameof ( IsProfileUri ) ) ) ;
2016-12-23 03:01:37 +01:00
2019-01-10 22:33:07 +01:00
return await UrlGetToJsonObjectWithSession < T > ( host , request , checkSessionPreemptively , - - maxTries ) . ConfigureAwait ( false ) ;
}
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return response . Content ;
2016-12-23 03:01:37 +01:00
}
2019-01-10 22:33:07 +01:00
[PublicAPI]
public async Task < XmlDocument > UrlGetToXmlDocumentWithSession ( string host , string request , bool checkSessionPreemptively = true , byte maxTries = WebBrowser . MaxTries ) {
if ( string . IsNullOrEmpty ( host ) | | string . IsNullOrEmpty ( request ) ) {
Bot . ArchiLogger . LogNullError ( nameof ( host ) + " || " + nameof ( request ) ) ;
2018-12-15 00:27:15 +01:00
2016-11-24 07:32:16 +01:00
return null ;
2016-07-01 16:33:27 +02:00
}
2019-01-10 22:33:07 +01:00
if ( maxTries = = 0 ) {
Bot . ArchiLogger . LogGenericWarning ( string . Format ( Strings . ErrorRequestFailedTooManyTimes , WebBrowser . MaxTries ) ) ;
Bot . ArchiLogger . LogGenericDebug ( string . Format ( Strings . ErrorFailingRequest , host + request ) ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return null ;
}
2017-04-05 17:01:18 +02:00
2019-01-10 22:33:07 +01:00
if ( checkSessionPreemptively ) {
// Check session preemptively as this request might not get redirected to expiration
bool? sessionExpired = await IsSessionExpired ( ) . ConfigureAwait ( false ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
if ( sessionExpired . GetValueOrDefault ( true ) ) {
if ( await RefreshSession ( ) . ConfigureAwait ( false ) ) {
return await UrlGetToXmlDocumentWithSession ( host , request , true , - - maxTries ) . ConfigureAwait ( false ) ;
2017-04-05 17:01:18 +02:00
}
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
Bot . ArchiLogger . LogGenericWarning ( Strings . WarningFailed ) ;
Bot . ArchiLogger . LogGenericDebug ( string . Format ( Strings . ErrorFailingRequest , host + request ) ) ;
2018-12-15 00:27:15 +01:00
2016-11-24 07:32:16 +01:00
return null ;
2016-07-01 16:33:27 +02:00
}
2019-01-10 22:33:07 +01:00
} else {
// If session refresh is already in progress, just wait for it
await SessionSemaphore . WaitAsync ( ) . ConfigureAwait ( false ) ;
SessionSemaphore . Release ( ) ;
}
2016-07-01 16:33:27 +02:00
2019-01-10 22:33:07 +01:00
if ( SteamID = = 0 ) {
2019-01-14 21:50:23 +01:00
for ( byte i = 0 ; ( i < ASF . GlobalConfig . ConnectionTimeout ) & & ( SteamID = = 0 ) & & Bot . IsConnectedAndLoggedOn ; i + + ) {
2019-01-10 22:33:07 +01:00
await Task . Delay ( 1000 ) . ConfigureAwait ( false ) ;
2016-11-24 07:32:16 +01:00
}
2016-07-01 16:33:27 +02:00
2019-01-10 22:33:07 +01:00
if ( SteamID = = 0 ) {
Bot . ArchiLogger . LogGenericWarning ( Strings . WarningFailed ) ;
Bot . ArchiLogger . LogGenericDebug ( string . Format ( Strings . ErrorFailingRequest , host + request ) ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return null ;
2016-11-24 07:32:16 +01:00
}
}
2016-04-12 16:58:45 +02:00
2019-01-10 22:33:07 +01:00
WebBrowser . XmlDocumentResponse response = await WebLimitRequest ( host , async ( ) = > await WebBrowser . UrlGetToXmlDocument ( host + request ) . ConfigureAwait ( false ) ) . ConfigureAwait ( false ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
if ( response = = null ) {
return null ;
}
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
if ( IsSessionExpiredUri ( response . FinalUri ) ) {
if ( await RefreshSession ( ) . ConfigureAwait ( false ) ) {
return await UrlGetToXmlDocumentWithSession ( host , request , checkSessionPreemptively , - - maxTries ) . ConfigureAwait ( false ) ;
2016-11-24 07:32:16 +01:00
}
2015-10-25 06:16:50 +01:00
2019-01-10 22:33:07 +01:00
Bot . ArchiLogger . LogGenericWarning ( Strings . WarningFailed ) ;
Bot . ArchiLogger . LogGenericDebug ( string . Format ( Strings . ErrorFailingRequest , host + request ) ) ;
2016-07-08 07:41:36 +02:00
2019-01-10 22:33:07 +01:00
return null ;
}
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
// Under special brain-damaged circumstances, Steam might just return our own profile as a response to the request, for absolutely no reason whatsoever - just try again in this case
if ( await IsProfileUri ( response . FinalUri ) . ConfigureAwait ( false ) ) {
Bot . ArchiLogger . LogGenericDebug ( string . Format ( Strings . WarningWorkaroundTriggered , nameof ( IsProfileUri ) ) ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return await UrlGetToXmlDocumentWithSession ( host , request , checkSessionPreemptively , - - maxTries ) . ConfigureAwait ( false ) ;
}
2016-06-20 13:45:12 +02:00
2019-01-10 22:33:07 +01:00
return response . Content ;
}
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
[PublicAPI]
public async Task < bool > UrlHeadWithSession ( string host , string request , bool checkSessionPreemptively = true , byte maxTries = WebBrowser . MaxTries ) {
if ( string . IsNullOrEmpty ( host ) | | string . IsNullOrEmpty ( request ) ) {
Bot . ArchiLogger . LogNullError ( nameof ( host ) + " || " + nameof ( request ) ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return false ;
}
2015-10-25 06:16:50 +01:00
2019-01-10 22:33:07 +01:00
if ( maxTries = = 0 ) {
Bot . ArchiLogger . LogGenericWarning ( string . Format ( Strings . ErrorRequestFailedTooManyTimes , WebBrowser . MaxTries ) ) ;
Bot . ArchiLogger . LogGenericDebug ( string . Format ( Strings . ErrorFailingRequest , host + request ) ) ;
2015-10-25 06:16:50 +01:00
2019-01-10 22:33:07 +01:00
return false ;
}
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
if ( checkSessionPreemptively ) {
// Check session preemptively as this request might not get redirected to expiration
bool? sessionExpired = await IsSessionExpired ( ) . ConfigureAwait ( false ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
if ( sessionExpired . GetValueOrDefault ( true ) ) {
if ( await RefreshSession ( ) . ConfigureAwait ( false ) ) {
return await UrlHeadWithSession ( host , request , true , - - maxTries ) . ConfigureAwait ( false ) ;
2016-11-24 07:32:16 +01:00
}
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
Bot . ArchiLogger . LogGenericWarning ( Strings . WarningFailed ) ;
Bot . ArchiLogger . LogGenericDebug ( string . Format ( Strings . ErrorFailingRequest , host + request ) ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return false ;
2016-11-24 07:32:16 +01:00
}
2019-01-10 22:33:07 +01:00
} else {
// If session refresh is already in progress, just wait for it
await SessionSemaphore . WaitAsync ( ) . ConfigureAwait ( false ) ;
SessionSemaphore . Release ( ) ;
2015-10-25 06:16:50 +01:00
}
2019-01-10 22:33:07 +01:00
if ( SteamID = = 0 ) {
2019-01-14 21:50:23 +01:00
for ( byte i = 0 ; ( i < ASF . GlobalConfig . ConnectionTimeout ) & & ( SteamID = = 0 ) & & Bot . IsConnectedAndLoggedOn ; i + + ) {
2019-01-10 22:33:07 +01:00
await Task . Delay ( 1000 ) . ConfigureAwait ( false ) ;
}
2018-07-04 19:13:51 +02:00
2019-01-10 22:33:07 +01:00
if ( SteamID = = 0 ) {
Bot . ArchiLogger . LogGenericWarning ( Strings . WarningFailed ) ;
Bot . ArchiLogger . LogGenericDebug ( string . Format ( Strings . ErrorFailingRequest , host + request ) ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return false ;
2018-07-04 19:13:51 +02:00
}
}
2019-01-10 22:33:07 +01:00
WebBrowser . BasicResponse response = await WebLimitRequest ( host , async ( ) = > await WebBrowser . UrlHead ( host + request ) . ConfigureAwait ( false ) ) . ConfigureAwait ( false ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
if ( response = = null ) {
return false ;
2018-07-04 19:13:51 +02:00
}
2019-01-10 22:33:07 +01:00
if ( IsSessionExpiredUri ( response . FinalUri ) ) {
if ( await RefreshSession ( ) . ConfigureAwait ( false ) ) {
return await UrlHeadWithSession ( host , request , checkSessionPreemptively , - - maxTries ) . ConfigureAwait ( false ) ;
}
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
Bot . ArchiLogger . LogGenericWarning ( Strings . WarningFailed ) ;
Bot . ArchiLogger . LogGenericDebug ( string . Format ( Strings . ErrorFailingRequest , host + request ) ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return false ;
2018-07-04 19:13:51 +02:00
}
2019-01-10 22:33:07 +01:00
// Under special brain-damaged circumstances, Steam might just return our own profile as a response to the request, for absolutely no reason whatsoever - just try again in this case
if ( await IsProfileUri ( response . FinalUri ) . ConfigureAwait ( false ) ) {
Bot . ArchiLogger . LogGenericDebug ( string . Format ( Strings . WarningWorkaroundTriggered , nameof ( IsProfileUri ) ) ) ;
2018-07-04 19:13:51 +02:00
2019-01-10 22:33:07 +01:00
return await UrlHeadWithSession ( host , request , checkSessionPreemptively , - - maxTries ) . ConfigureAwait ( false ) ;
2018-07-04 19:13:51 +02:00
}
2019-01-10 22:33:07 +01:00
return true ;
2018-07-04 19:13:51 +02:00
}
2019-01-10 22:33:07 +01:00
[PublicAPI]
public async Task < HtmlDocument > UrlPostToHtmlDocumentWithSession ( string host , string request , Dictionary < string , string > data = null , string referer = null , ESession session = ESession . Lowercase , bool checkSessionPreemptively = true , byte maxTries = WebBrowser . MaxTries ) {
if ( string . IsNullOrEmpty ( host ) | | string . IsNullOrEmpty ( request ) ) {
Bot . ArchiLogger . LogNullError ( nameof ( host ) + " || " + nameof ( request ) ) ;
2018-12-15 00:27:15 +01:00
2016-11-24 07:32:16 +01:00
return null ;
2015-10-25 06:16:50 +01:00
}
2019-01-10 22:33:07 +01:00
if ( maxTries = = 0 ) {
Bot . ArchiLogger . LogGenericWarning ( string . Format ( Strings . ErrorRequestFailedTooManyTimes , WebBrowser . MaxTries ) ) ;
Bot . ArchiLogger . LogGenericDebug ( string . Format ( Strings . ErrorFailingRequest , host + request ) ) ;
2018-12-15 00:27:15 +01:00
2016-11-24 07:32:16 +01:00
return null ;
2016-08-06 16:29:05 +02:00
}
2015-10-25 06:16:50 +01:00
2019-01-10 22:33:07 +01:00
if ( checkSessionPreemptively ) {
// Check session preemptively as this request might not get redirected to expiration
bool? sessionExpired = await IsSessionExpired ( ) . ConfigureAwait ( false ) ;
if ( sessionExpired . GetValueOrDefault ( true ) ) {
if ( await RefreshSession ( ) . ConfigureAwait ( false ) ) {
return await UrlPostToHtmlDocumentWithSession ( host , request , data , referer , session , true , - - maxTries ) . ConfigureAwait ( false ) ;
}
2018-06-27 21:18:54 +02:00
Bot . ArchiLogger . LogGenericWarning ( Strings . WarningFailed ) ;
2019-01-10 22:33:07 +01:00
Bot . ArchiLogger . LogGenericDebug ( string . Format ( Strings . ErrorFailingRequest , host + request ) ) ;
2018-12-15 00:27:15 +01:00
2018-06-27 21:18:54 +02:00
return null ;
}
2019-01-10 22:33:07 +01:00
} else {
// If session refresh is already in progress, just wait for it
await SessionSemaphore . WaitAsync ( ) . ConfigureAwait ( false ) ;
SessionSemaphore . Release ( ) ;
2016-11-24 07:32:16 +01:00
}
2016-08-06 16:29:05 +02:00
2018-06-27 21:18:54 +02:00
if ( SteamID = = 0 ) {
2019-01-14 21:50:23 +01:00
for ( byte i = 0 ; ( i < ASF . GlobalConfig . ConnectionTimeout ) & & ( SteamID = = 0 ) & & Bot . IsConnectedAndLoggedOn ; i + + ) {
2018-06-27 21:18:54 +02:00
await Task . Delay ( 1000 ) . ConfigureAwait ( false ) ;
}
if ( SteamID = = 0 ) {
Bot . ArchiLogger . LogGenericWarning ( Strings . WarningFailed ) ;
2019-01-10 22:33:07 +01:00
Bot . ArchiLogger . LogGenericDebug ( string . Format ( Strings . ErrorFailingRequest , host + request ) ) ;
2018-12-15 00:27:15 +01:00
2018-06-27 21:18:54 +02:00
return null ;
}
}
2019-01-10 22:33:07 +01:00
if ( session ! = ESession . None ) {
string sessionID = WebBrowser . CookieContainer . GetCookieValue ( host , "sessionid" ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
if ( string . IsNullOrEmpty ( sessionID ) ) {
Bot . ArchiLogger . LogNullError ( nameof ( sessionID ) ) ;
2015-10-25 06:16:50 +01:00
2019-01-10 22:33:07 +01:00
return null ;
}
2018-08-20 04:33:09 +03:00
2019-01-10 22:33:07 +01:00
string sessionName ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
switch ( session ) {
case ESession . CamelCase :
sessionName = "sessionID" ;
2018-09-01 09:46:33 +03:00
2019-01-10 22:33:07 +01:00
break ;
case ESession . Lowercase :
sessionName = "sessionid" ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
break ;
default :
Bot . ArchiLogger . LogGenericError ( string . Format ( Strings . WarningUnknownValuePleaseReport , nameof ( session ) , session ) ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return null ;
2018-08-20 04:33:09 +03:00
}
2019-01-10 22:33:07 +01:00
if ( data ! = null ) {
data [ sessionName ] = sessionID ;
} else {
data = new Dictionary < string , string > ( 1 ) { { sessionName , sessionID } } ;
2018-08-20 04:33:09 +03:00
}
2019-01-10 22:33:07 +01:00
}
2018-08-20 04:33:09 +03:00
2019-01-10 22:33:07 +01:00
WebBrowser . HtmlDocumentResponse response = await WebLimitRequest ( host , async ( ) = > await WebBrowser . UrlPostToHtmlDocument ( host + request , data , referer ) . ConfigureAwait ( false ) ) . ConfigureAwait ( false ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
if ( response = = null ) {
return null ;
}
if ( IsSessionExpiredUri ( response . FinalUri ) ) {
if ( await RefreshSession ( ) . ConfigureAwait ( false ) ) {
return await UrlPostToHtmlDocumentWithSession ( host , request , data , referer , session , checkSessionPreemptively , - - maxTries ) . ConfigureAwait ( false ) ;
2018-08-20 04:33:09 +03:00
}
2019-01-10 22:33:07 +01:00
Bot . ArchiLogger . LogGenericWarning ( Strings . WarningFailed ) ;
Bot . ArchiLogger . LogGenericDebug ( string . Format ( Strings . ErrorFailingRequest , host + request ) ) ;
return null ;
2018-08-20 04:33:09 +03:00
}
2019-01-10 22:33:07 +01:00
// Under special brain-damaged circumstances, Steam might just return our own profile as a response to the request, for absolutely no reason whatsoever - just try again in this case
if ( await IsProfileUri ( response . FinalUri ) . ConfigureAwait ( false ) ) {
Bot . ArchiLogger . LogGenericDebug ( string . Format ( Strings . WarningWorkaroundTriggered , nameof ( IsProfileUri ) ) ) ;
2018-08-20 04:33:09 +03:00
2019-01-10 22:33:07 +01:00
return await UrlPostToHtmlDocumentWithSession ( host , request , data , referer , session , checkSessionPreemptively , - - maxTries ) . ConfigureAwait ( false ) ;
}
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return response . Content ;
2016-12-23 03:01:37 +01:00
}
2019-01-10 22:33:07 +01:00
[PublicAPI]
public async Task < T > UrlPostToJsonObjectWithSession < T > ( string host , string request , Dictionary < string , string > data = null , string referer = null , ESession session = ESession . Lowercase , bool checkSessionPreemptively = true , byte maxTries = WebBrowser . MaxTries ) where T : class {
if ( string . IsNullOrEmpty ( host ) | | string . IsNullOrEmpty ( request ) ) {
Bot . ArchiLogger . LogNullError ( nameof ( host ) + " || " + nameof ( request ) ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return null ;
2016-09-30 01:44:14 +02:00
}
2019-01-10 22:33:07 +01:00
if ( maxTries = = 0 ) {
Bot . ArchiLogger . LogGenericWarning ( string . Format ( Strings . ErrorRequestFailedTooManyTimes , WebBrowser . MaxTries ) ) ;
Bot . ArchiLogger . LogGenericDebug ( string . Format ( Strings . ErrorFailingRequest , host + request ) ) ;
2016-09-30 01:44:14 +02:00
2019-01-10 22:33:07 +01:00
return null ;
}
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
if ( checkSessionPreemptively ) {
// Check session preemptively as this request might not get redirected to expiration
bool? sessionExpired = await IsSessionExpired ( ) . ConfigureAwait ( false ) ;
2016-09-30 01:44:14 +02:00
2019-01-10 22:33:07 +01:00
if ( sessionExpired . GetValueOrDefault ( true ) ) {
if ( await RefreshSession ( ) . ConfigureAwait ( false ) ) {
return await UrlPostToJsonObjectWithSession < T > ( host , request , data , referer , session , true , - - maxTries ) . ConfigureAwait ( false ) ;
}
Bot . ArchiLogger . LogGenericWarning ( Strings . WarningFailed ) ;
Bot . ArchiLogger . LogGenericDebug ( string . Format ( Strings . ErrorFailingRequest , host + request ) ) ;
2018-12-15 00:27:15 +01:00
2016-09-30 01:44:14 +02:00
return null ;
}
2019-01-10 22:33:07 +01:00
} else {
// If session refresh is already in progress, just wait for it
await SessionSemaphore . WaitAsync ( ) . ConfigureAwait ( false ) ;
SessionSemaphore . Release ( ) ;
2016-09-30 01:44:14 +02:00
}
2019-01-10 22:33:07 +01:00
if ( SteamID = = 0 ) {
2019-01-14 21:50:23 +01:00
for ( byte i = 0 ; ( i < ASF . GlobalConfig . ConnectionTimeout ) & & ( SteamID = = 0 ) & & Bot . IsConnectedAndLoggedOn ; i + + ) {
2019-01-10 22:33:07 +01:00
await Task . Delay ( 1000 ) . ConfigureAwait ( false ) ;
}
2016-09-30 01:44:14 +02:00
2019-01-10 22:33:07 +01:00
if ( SteamID = = 0 ) {
Bot . ArchiLogger . LogGenericWarning ( Strings . WarningFailed ) ;
Bot . ArchiLogger . LogGenericDebug ( string . Format ( Strings . ErrorFailingRequest , host + request ) ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return null ;
}
2016-11-17 21:30:17 +01:00
}
2019-01-10 22:33:07 +01:00
if ( session ! = ESession . None ) {
string sessionID = WebBrowser . CookieContainer . GetCookieValue ( host , "sessionid" ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
if ( string . IsNullOrEmpty ( sessionID ) ) {
Bot . ArchiLogger . LogNullError ( nameof ( sessionID ) ) ;
2016-11-24 07:32:16 +01:00
2019-01-10 22:33:07 +01:00
return null ;
}
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
string sessionName ;
2018-01-12 04:05:06 +01:00
2019-01-10 22:33:07 +01:00
switch ( session ) {
case ESession . CamelCase :
sessionName = "sessionID" ;
2018-06-27 21:18:54 +02:00
2019-01-10 22:33:07 +01:00
break ;
case ESession . Lowercase :
sessionName = "sessionid" ;
break ;
default :
Bot . ArchiLogger . LogGenericError ( string . Format ( Strings . WarningUnknownValuePleaseReport , nameof ( session ) , session ) ) ;
2018-12-15 00:27:15 +01:00
2018-06-27 21:18:54 +02:00
return null ;
}
2019-01-10 22:33:07 +01:00
if ( data ! = null ) {
data [ sessionName ] = sessionID ;
} else {
data = new Dictionary < string , string > ( 1 ) { { sessionName , sessionID } } ;
}
2018-06-27 21:18:54 +02:00
}
2019-01-10 22:33:07 +01:00
WebBrowser . ObjectResponse < T > response = await WebLimitRequest ( host , async ( ) = > await WebBrowser . UrlPostToJsonObject < T > ( host + request , data , referer ) . ConfigureAwait ( false ) ) . ConfigureAwait ( false ) ;
2016-11-17 21:30:17 +01:00
2019-01-10 22:33:07 +01:00
if ( response = = null ) {
return null ;
}
2017-09-18 16:45:31 +02:00
2019-01-10 22:33:07 +01:00
if ( IsSessionExpiredUri ( response . FinalUri ) ) {
if ( await RefreshSession ( ) . ConfigureAwait ( false ) ) {
return await UrlPostToJsonObjectWithSession < T > ( host , request , data , referer , session , checkSessionPreemptively , - - maxTries ) . ConfigureAwait ( false ) ;
}
2018-12-08 22:21:44 +01:00
2019-01-10 22:33:07 +01:00
Bot . ArchiLogger . LogGenericWarning ( Strings . WarningFailed ) ;
Bot . ArchiLogger . LogGenericDebug ( string . Format ( Strings . ErrorFailingRequest , host + request ) ) ;
2016-03-29 14:33:05 +02:00
2019-01-10 22:33:07 +01:00
return null ;
}
2015-12-16 22:05:42 +01:00
2019-01-10 22:33:07 +01:00
// Under special brain-damaged circumstances, Steam might just return our own profile as a response to the request, for absolutely no reason whatsoever - just try again in this case
if ( await IsProfileUri ( response . FinalUri ) . ConfigureAwait ( false ) ) {
Bot . ArchiLogger . LogGenericDebug ( string . Format ( Strings . WarningWorkaroundTriggered , nameof ( IsProfileUri ) ) ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return await UrlPostToJsonObjectWithSession < T > ( host , request , data , referer , session , checkSessionPreemptively , - - maxTries ) . ConfigureAwait ( false ) ;
}
2017-09-18 19:18:22 +02:00
2019-01-10 22:33:07 +01:00
return response . Content ;
}
2017-09-17 18:24:05 +02:00
2019-01-10 22:33:07 +01:00
[PublicAPI]
public async Task < T > UrlPostToJsonObjectWithSession < T > ( string host , string request , List < KeyValuePair < string , string > > data = null , string referer = null , ESession session = ESession . Lowercase , bool checkSessionPreemptively = true , byte maxTries = WebBrowser . MaxTries ) where T : class {
if ( string . IsNullOrEmpty ( host ) | | string . IsNullOrEmpty ( request ) ) {
Bot . ArchiLogger . LogNullError ( nameof ( host ) + " || " + nameof ( request ) ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return null ;
}
2017-09-18 19:18:22 +02:00
2019-01-10 22:33:07 +01:00
if ( maxTries = = 0 ) {
Bot . ArchiLogger . LogGenericWarning ( string . Format ( Strings . ErrorRequestFailedTooManyTimes , WebBrowser . MaxTries ) ) ;
Bot . ArchiLogger . LogGenericDebug ( string . Format ( Strings . ErrorFailingRequest , host + request ) ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return null ;
}
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
if ( checkSessionPreemptively ) {
// Check session preemptively as this request might not get redirected to expiration
bool? sessionExpired = await IsSessionExpired ( ) . ConfigureAwait ( false ) ;
2016-06-19 05:40:46 +02:00
2019-01-10 22:33:07 +01:00
if ( sessionExpired . GetValueOrDefault ( true ) ) {
if ( await RefreshSession ( ) . ConfigureAwait ( false ) ) {
return await UrlPostToJsonObjectWithSession < T > ( host , request , data , referer , session , true , - - maxTries ) . ConfigureAwait ( false ) ;
}
2016-06-19 05:40:46 +02:00
2019-01-10 22:33:07 +01:00
Bot . ArchiLogger . LogGenericWarning ( Strings . WarningFailed ) ;
Bot . ArchiLogger . LogGenericDebug ( string . Format ( Strings . ErrorFailingRequest , host + request ) ) ;
2018-06-27 21:18:54 +02:00
2019-01-10 22:33:07 +01:00
return null ;
}
} else {
// If session refresh is already in progress, just wait for it
await SessionSemaphore . WaitAsync ( ) . ConfigureAwait ( false ) ;
SessionSemaphore . Release ( ) ;
}
2018-06-27 21:18:54 +02:00
2019-01-10 22:33:07 +01:00
if ( SteamID = = 0 ) {
2019-01-14 21:50:23 +01:00
for ( byte i = 0 ; ( i < ASF . GlobalConfig . ConnectionTimeout ) & & ( SteamID = = 0 ) & & Bot . IsConnectedAndLoggedOn ; i + + ) {
2019-01-10 22:33:07 +01:00
await Task . Delay ( 1000 ) . ConfigureAwait ( false ) ;
}
2016-06-19 09:34:09 +02:00
2019-01-10 22:33:07 +01:00
if ( SteamID = = 0 ) {
Bot . ArchiLogger . LogGenericWarning ( Strings . WarningFailed ) ;
Bot . ArchiLogger . LogGenericDebug ( string . Format ( Strings . ErrorFailingRequest , host + request ) ) ;
2016-06-19 05:40:46 +02:00
2019-01-10 22:33:07 +01:00
return null ;
}
}
2016-06-19 05:40:46 +02:00
2019-01-10 22:33:07 +01:00
if ( session ! = ESession . None ) {
string sessionID = WebBrowser . CookieContainer . GetCookieValue ( host , "sessionid" ) ;
2016-08-21 22:35:31 +02:00
2019-01-10 22:33:07 +01:00
if ( string . IsNullOrEmpty ( sessionID ) ) {
Bot . ArchiLogger . LogNullError ( nameof ( sessionID ) ) ;
Enhance InventoryLimiterDelay
Gifts/Login limiter is actually working decent, because response is nearly instant and fast enough to not worry about it in long-run.
With inventory things are entirely different, as inventory fetching might take even a very long time, and while fetching one inventory, we might already run out of our delay and start fetching another one.
This is not a big functionality-wise, as it's nothing new for ASF to parse multiple inventories concurrently, but Steam Community actually counts number of requests, and our inventory function might ask for multiple pages during execution, which could quickly lead to a situation of 10+ ongoing inventory requests being sent concurrently for too many accounts at once, as we can't predict not only how long the request will be handled, but also how many sub-requests we will do across one.
This means that for optimal performance in terms of rate-limiting, we must limit ASF to one inventory request at a time, with mandatory InventoryLimiterDelay before asking for another one.
This can degrade performance of previously fast !loot requests on multiple accounts at once (especially with bigger inventories), but it will also decrease significantly a chance of getting rate-limited and requests failing.
2017-04-08 04:47:38 +02:00
2019-01-10 22:33:07 +01:00
return null ;
}
2016-06-19 05:40:46 +02:00
2019-01-10 22:33:07 +01:00
string sessionName ;
2016-12-25 05:52:17 +01:00
2019-01-10 22:33:07 +01:00
switch ( session ) {
case ESession . CamelCase :
sessionName = "sessionID" ;
2016-06-19 09:34:09 +02:00
2019-01-10 22:33:07 +01:00
break ;
case ESession . Lowercase :
sessionName = "sessionid" ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
break ;
default :
Bot . ArchiLogger . LogGenericError ( string . Format ( Strings . WarningUnknownValuePleaseReport , nameof ( session ) , session ) ) ;
2016-07-16 21:03:39 +02:00
2019-01-10 22:33:07 +01:00
return null ;
2017-06-26 01:47:25 +02:00
}
2016-06-19 05:40:46 +02:00
2019-01-10 22:33:07 +01:00
KeyValuePair < string , string > sessionValue = new KeyValuePair < string , string > ( sessionName , sessionID ) ;
2018-01-08 01:48:57 +01:00
2019-01-10 22:33:07 +01:00
if ( data ! = null ) {
data . Remove ( sessionValue ) ;
data . Add ( sessionValue ) ;
} else {
data = new List < KeyValuePair < string , string > > ( 1 ) { sessionValue } ;
}
}
2018-01-08 01:48:57 +01:00
2019-01-10 22:33:07 +01:00
WebBrowser . ObjectResponse < T > response = await WebLimitRequest ( host , async ( ) = > await WebBrowser . UrlPostToJsonObject < T > ( host + request , data , referer ) . ConfigureAwait ( false ) ) . ConfigureAwait ( false ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
if ( response = = null ) {
2018-01-08 01:48:57 +01:00
return null ;
}
2019-01-10 22:33:07 +01:00
if ( IsSessionExpiredUri ( response . FinalUri ) ) {
if ( await RefreshSession ( ) . ConfigureAwait ( false ) ) {
return await UrlPostToJsonObjectWithSession < T > ( host , request , data , referer , session , checkSessionPreemptively , - - maxTries ) . ConfigureAwait ( false ) ;
2018-01-08 01:48:57 +01:00
}
2019-01-10 22:33:07 +01:00
Bot . ArchiLogger . LogGenericWarning ( Strings . WarningFailed ) ;
Bot . ArchiLogger . LogGenericDebug ( string . Format ( Strings . ErrorFailingRequest , host + request ) ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return null ;
}
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
// Under special brain-damaged circumstances, Steam might just return our own profile as a response to the request, for absolutely no reason whatsoever - just try again in this case
if ( await IsProfileUri ( response . FinalUri ) . ConfigureAwait ( false ) ) {
Bot . ArchiLogger . LogGenericDebug ( string . Format ( Strings . WarningWorkaroundTriggered , nameof ( IsProfileUri ) ) ) ;
2018-01-08 01:48:57 +01:00
2019-01-10 22:33:07 +01:00
return await UrlPostToJsonObjectWithSession < T > ( host , request , data , referer , session , checkSessionPreemptively , - - maxTries ) . ConfigureAwait ( false ) ;
2018-01-08 01:48:57 +01:00
}
2019-01-10 22:33:07 +01:00
return response . Content ;
2018-01-08 01:48:57 +01:00
}
2019-01-10 22:33:07 +01:00
[PublicAPI]
public async Task < bool > UrlPostWithSession ( string host , string request , Dictionary < string , string > data = null , string referer = null , ESession session = ESession . Lowercase , bool checkSessionPreemptively = true , byte maxTries = WebBrowser . MaxTries ) {
if ( string . IsNullOrEmpty ( host ) | | string . IsNullOrEmpty ( request ) ) {
Bot . ArchiLogger . LogNullError ( nameof ( host ) + " || " + nameof ( request ) ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return false ;
2016-03-15 04:20:28 +01:00
}
2019-01-10 22:33:07 +01:00
if ( maxTries = = 0 ) {
Bot . ArchiLogger . LogGenericWarning ( string . Format ( Strings . ErrorRequestFailedTooManyTimes , WebBrowser . MaxTries ) ) ;
Bot . ArchiLogger . LogGenericDebug ( string . Format ( Strings . ErrorFailingRequest , host + request ) ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return false ;
2016-04-29 15:44:11 +02:00
}
2019-01-10 22:33:07 +01:00
if ( checkSessionPreemptively ) {
// Check session preemptively as this request might not get redirected to expiration
bool? sessionExpired = await IsSessionExpired ( ) . ConfigureAwait ( false ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
if ( sessionExpired . GetValueOrDefault ( true ) ) {
if ( await RefreshSession ( ) . ConfigureAwait ( false ) ) {
return await UrlPostWithSession ( host , request , data , referer , session , true , - - maxTries ) . ConfigureAwait ( false ) ;
}
2017-04-05 17:01:18 +02:00
2019-01-10 22:33:07 +01:00
Bot . ArchiLogger . LogGenericWarning ( Strings . WarningFailed ) ;
Bot . ArchiLogger . LogGenericDebug ( string . Format ( Strings . ErrorFailingRequest , host + request ) ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return false ;
2017-09-09 20:24:57 +02:00
}
2019-01-10 22:33:07 +01:00
} else {
// If session refresh is already in progress, just wait for it
await SessionSemaphore . WaitAsync ( ) . ConfigureAwait ( false ) ;
SessionSemaphore . Release ( ) ;
2016-04-29 15:44:11 +02:00
}
2019-01-10 22:33:07 +01:00
if ( SteamID = = 0 ) {
2019-01-14 21:50:23 +01:00
for ( byte i = 0 ; ( i < ASF . GlobalConfig . ConnectionTimeout ) & & ( SteamID = = 0 ) & & Bot . IsConnectedAndLoggedOn ; i + + ) {
2019-01-10 22:33:07 +01:00
await Task . Delay ( 1000 ) . ConfigureAwait ( false ) ;
}
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
if ( SteamID = = 0 ) {
Bot . ArchiLogger . LogGenericWarning ( Strings . WarningFailed ) ;
Bot . ArchiLogger . LogGenericDebug ( string . Format ( Strings . ErrorFailingRequest , host + request ) ) ;
return false ;
}
2016-04-29 15:44:11 +02:00
}
2019-01-10 22:33:07 +01:00
if ( session ! = ESession . None ) {
string sessionID = WebBrowser . CookieContainer . GetCookieValue ( host , "sessionid" ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
if ( string . IsNullOrEmpty ( sessionID ) ) {
Bot . ArchiLogger . LogNullError ( nameof ( sessionID ) ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return false ;
}
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
string sessionName ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
switch ( session ) {
case ESession . CamelCase :
sessionName = "sessionID" ;
break ;
case ESession . Lowercase :
sessionName = "sessionid" ;
break ;
default :
Bot . ArchiLogger . LogGenericError ( string . Format ( Strings . WarningUnknownValuePleaseReport , nameof ( session ) , session ) ) ;
return false ;
2016-04-29 15:44:11 +02:00
}
2019-01-10 22:33:07 +01:00
if ( data ! = null ) {
data [ sessionName ] = sessionID ;
} else {
data = new Dictionary < string , string > ( 1 ) { { sessionName , sessionID } } ;
}
2016-04-29 15:44:11 +02:00
}
2019-01-10 22:33:07 +01:00
WebBrowser . BasicResponse response = await WebLimitRequest ( host , async ( ) = > await WebBrowser . UrlPost ( host + request , data , referer ) . ConfigureAwait ( false ) ) . ConfigureAwait ( false ) ;
2016-04-29 15:44:11 +02:00
2019-01-10 22:33:07 +01:00
if ( response = = null ) {
return false ;
}
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
if ( IsSessionExpiredUri ( response . FinalUri ) ) {
if ( await RefreshSession ( ) . ConfigureAwait ( false ) ) {
return await UrlPostWithSession ( host , request , data , referer , session , checkSessionPreemptively , - - maxTries ) . ConfigureAwait ( false ) ;
}
2017-04-05 17:01:18 +02:00
2019-01-10 22:33:07 +01:00
Bot . ArchiLogger . LogGenericWarning ( Strings . WarningFailed ) ;
Bot . ArchiLogger . LogGenericDebug ( string . Format ( Strings . ErrorFailingRequest , host + request ) ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return false ;
2016-06-19 05:40:46 +02:00
}
2019-01-10 22:33:07 +01:00
// Under special brain-damaged circumstances, Steam might just return our own profile as a response to the request, for absolutely no reason whatsoever - just try again in this case
if ( await IsProfileUri ( response . FinalUri ) . ConfigureAwait ( false ) ) {
Bot . ArchiLogger . LogGenericDebug ( string . Format ( Strings . WarningWorkaroundTriggered , nameof ( IsProfileUri ) ) ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return await UrlPostWithSession ( host , request , data , referer , session , checkSessionPreemptively , - - maxTries ) . ConfigureAwait ( false ) ;
2018-03-09 15:43:25 +01:00
}
2019-01-10 22:33:07 +01:00
return true ;
}
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
internal async Task < bool > AcceptDigitalGiftCard ( ulong giftCardID ) {
if ( giftCardID = = 0 ) {
Bot . ArchiLogger . LogNullError ( nameof ( giftCardID ) ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return false ;
2016-06-19 05:40:46 +02:00
}
2019-01-10 22:33:07 +01:00
const string request = "/gifts/0/resolvegiftcard" ;
// Extra entry for sessionID
Dictionary < string , string > data = new Dictionary < string , string > ( 3 ) {
{ "accept" , "1" } ,
{ "giftcardid" , giftCardID . ToString ( ) }
} ;
Steam . NumberResponse result = await UrlPostToJsonObjectWithSession < Steam . NumberResponse > ( SteamStoreURL , request , data ) . ConfigureAwait ( false ) ;
return result ? . Success = = true ;
2016-06-19 05:40:46 +02:00
}
2019-01-10 22:33:07 +01:00
internal async Task < ( bool Success , bool RequiresMobileConfirmation ) > AcceptTradeOffer ( ulong tradeID ) {
2016-06-12 02:18:18 +02:00
if ( tradeID = = 0 ) {
2016-11-06 12:06:02 +01:00
Bot . ArchiLogger . LogNullError ( nameof ( tradeID ) ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return ( false , false ) ;
2016-06-12 02:18:18 +02:00
}
2019-01-10 22:33:07 +01:00
string request = "/tradeoffer/" + tradeID + "/accept" ;
string referer = SteamCommunityURL + "/tradeoffer/" + tradeID ;
2016-06-12 02:18:18 +02:00
2019-01-10 22:33:07 +01:00
// Extra entry for sessionID
Dictionary < string , string > data = new Dictionary < string , string > ( 3 ) {
{ "serverid" , "1" } ,
{ "tradeofferid" , tradeID . ToString ( ) }
} ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
Steam . TradeOfferAcceptResponse response = await UrlPostToJsonObjectWithSession < Steam . TradeOfferAcceptResponse > ( SteamCommunityURL , request , data , referer ) . ConfigureAwait ( false ) ;
return response ! = null ? ( true , response . RequiresMobileConfirmation ) : ( false , false ) ;
}
internal async Task < bool > AddFreeLicense ( uint subID ) {
if ( subID = = 0 ) {
Bot . ArchiLogger . LogNullError ( nameof ( subID ) ) ;
return false ;
2016-06-12 02:18:18 +02:00
}
2019-01-10 22:33:07 +01:00
const string request = "/checkout/addfreelicense" ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
// Extra entry for sessionID
Dictionary < string , string > data = new Dictionary < string , string > ( 3 ) {
{ "action" , "add_to_cart" } ,
{ "subid" , subID . ToString ( ) }
} ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
HtmlDocument htmlDocument = await UrlPostToHtmlDocumentWithSession ( SteamStoreURL , request , data ) . ConfigureAwait ( false ) ;
return htmlDocument ? . DocumentNode . SelectSingleNode ( "//div[@class='add_free_content_success_area']" ) ! = null ;
}
internal async Task < bool > ChangePrivacySettings ( Steam . UserPrivacy userPrivacy ) {
if ( userPrivacy = = null ) {
Bot . ArchiLogger . LogNullError ( nameof ( userPrivacy ) ) ;
return false ;
2016-06-12 02:18:18 +02:00
}
2019-01-10 22:33:07 +01:00
string profileURL = await GetAbsoluteProfileURL ( ) . ConfigureAwait ( false ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
if ( string . IsNullOrEmpty ( profileURL ) ) {
Bot . ArchiLogger . LogGenericWarning ( Strings . WarningFailed ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return false ;
2016-06-12 02:18:18 +02:00
}
2019-01-10 22:33:07 +01:00
string request = profileURL + "/ajaxsetprivacy" ;
2016-06-12 02:18:18 +02:00
2019-01-10 22:33:07 +01:00
// Extra entry for sessionID
Dictionary < string , string > data = new Dictionary < string , string > ( 3 ) {
{ "eCommentPermission" , ( ( byte ) userPrivacy . CommentPermission ) . ToString ( ) } ,
{ "Privacy" , JsonConvert . SerializeObject ( userPrivacy . Settings ) }
} ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
Steam . NumberResponse response = await UrlPostToJsonObjectWithSession < Steam . NumberResponse > ( SteamCommunityURL , request , data ) . ConfigureAwait ( false ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
if ( response = = null ) {
return false ;
2016-06-12 02:18:18 +02:00
}
2019-01-10 22:33:07 +01:00
if ( ! response . Success ) {
Bot . ArchiLogger . LogGenericWarning ( Strings . WarningFailed ) ;
2016-06-12 02:18:18 +02:00
2019-01-10 22:33:07 +01:00
return false ;
}
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return true ;
}
internal async Task < bool > ClearFromDiscoveryQueue ( uint appID ) {
if ( appID = = 0 ) {
Bot . ArchiLogger . LogNullError ( nameof ( appID ) ) ;
return false ;
2016-06-12 02:18:18 +02:00
}
2019-01-10 22:33:07 +01:00
string request = "/app/" + appID ;
// Extra entry for sessionID
Dictionary < string , string > data = new Dictionary < string , string > ( 2 ) { { "appid_to_clear_from_queue" , appID . ToString ( ) } } ;
return await UrlPostWithSession ( SteamStoreURL , request , data ) . ConfigureAwait ( false ) ;
2016-06-12 02:18:18 +02:00
}
2019-01-10 22:33:07 +01:00
internal async Task < bool > DeclineTradeOffer ( ulong tradeID ) {
if ( tradeID = = 0 ) {
Bot . ArchiLogger . LogNullError ( nameof ( tradeID ) ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return false ;
2018-02-02 21:22:45 +01:00
}
2018-12-12 22:19:52 +01:00
( bool success , string steamApiKey ) = await CachedApiKey . GetValue ( ) . ConfigureAwait ( false ) ;
2018-12-15 00:27:15 +01:00
2018-12-12 22:19:52 +01:00
if ( ! success | | string . IsNullOrEmpty ( steamApiKey ) ) {
2019-01-10 22:33:07 +01:00
return false ;
2018-02-02 21:22:45 +01:00
}
KeyValue response = null ;
2018-12-15 00:27:15 +01:00
2018-02-02 21:22:45 +01:00
for ( byte i = 0 ; ( i < WebBrowser . MaxTries ) & & ( response = = null ) ; i + + ) {
using ( dynamic iEconService = WebAPI . GetAsyncInterface ( IEconService , steamApiKey ) ) {
iEconService . Timeout = WebBrowser . Timeout ;
try {
2018-06-16 07:47:07 +02:00
response = await WebLimitRequest (
WebAPI . DefaultBaseAddress . Host ,
2018-12-15 00:27:15 +01:00
2018-04-13 09:17:27 +02:00
// ReSharper disable once AccessToDisposedClosure
2019-01-10 22:33:07 +01:00
async ( ) = > await iEconService . DeclineTradeOffer (
method : WebRequestMethods . Http . Post ,
tradeofferid : tradeID
2018-04-13 09:17:27 +02:00
)
) . ConfigureAwait ( false ) ;
2018-02-02 21:22:45 +01:00
} catch ( TaskCanceledException e ) {
Bot . ArchiLogger . LogGenericDebuggingException ( e ) ;
} catch ( Exception e ) {
Bot . ArchiLogger . LogGenericWarningException ( e ) ;
}
}
}
if ( response = = null ) {
Bot . ArchiLogger . LogGenericWarning ( string . Format ( Strings . ErrorRequestFailedTooManyTimes , WebBrowser . MaxTries ) ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return false ;
2018-02-02 21:22:45 +01:00
}
2019-01-10 22:33:07 +01:00
return true ;
}
2018-12-15 00:27:15 +01:00
2019-01-10 23:44:32 +01:00
[NotNull]
2019-01-10 22:33:07 +01:00
internal HttpClient GenerateDisposableHttpClient ( ) = > WebBrowser . GenerateDisposableHttpClient ( ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 23:44:32 +01:00
[ItemCanBeNull]
2019-01-10 22:33:07 +01:00
internal async Task < HashSet < uint > > GenerateNewDiscoveryQueue ( ) {
const string request = "/explore/generatenewdiscoveryqueue" ;
2018-02-02 21:22:45 +01:00
2019-01-10 22:33:07 +01:00
// Extra entry for sessionID
Dictionary < string , string > data = new Dictionary < string , string > ( 2 ) { { "queuetype" , "0" } } ;
Steam . NewDiscoveryQueueResponse output = await UrlPostToJsonObjectWithSession < Steam . NewDiscoveryQueueResponse > ( SteamStoreURL , request , data ) . ConfigureAwait ( false ) ;
return output ? . Queue ;
2018-02-02 21:22:45 +01:00
}
2019-01-10 23:44:32 +01:00
[ItemCanBeNull]
2019-01-10 22:33:07 +01:00
internal async Task < HashSet < Steam . TradeOffer > > GetActiveTradeOffers ( ) {
( bool success , string steamApiKey ) = await CachedApiKey . GetValue ( ) . ConfigureAwait ( false ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
if ( ! success | | string . IsNullOrEmpty ( steamApiKey ) ) {
2016-11-24 07:32:16 +01:00
return null ;
2016-10-12 22:56:19 +02:00
}
2019-01-10 22:33:07 +01:00
KeyValue response = null ;
2018-06-27 21:18:54 +02:00
2019-01-10 22:33:07 +01:00
for ( byte i = 0 ; ( i < WebBrowser . MaxTries ) & & ( response = = null ) ; i + + ) {
using ( dynamic iEconService = WebAPI . GetAsyncInterface ( IEconService , steamApiKey ) ) {
iEconService . Timeout = WebBrowser . Timeout ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
try {
response = await WebLimitRequest (
WebAPI . DefaultBaseAddress . Host ,
// ReSharper disable once AccessToDisposedClosure
async ( ) = > await iEconService . GetTradeOffers (
active_only : 1 ,
get_descriptions : 1 ,
get_received_offers : 1 ,
time_historical_cutoff : uint . MaxValue
)
) . ConfigureAwait ( false ) ;
} catch ( TaskCanceledException e ) {
Bot . ArchiLogger . LogGenericDebuggingException ( e ) ;
} catch ( Exception e ) {
Bot . ArchiLogger . LogGenericWarningException ( e ) ;
}
2018-06-27 21:18:54 +02:00
}
}
2019-01-10 22:33:07 +01:00
if ( response = = null ) {
Bot . ArchiLogger . LogGenericWarning ( string . Format ( Strings . ErrorRequestFailedTooManyTimes , WebBrowser . MaxTries ) ) ;
2018-12-15 00:27:15 +01:00
2015-10-28 20:01:43 +01:00
return null ;
}
2019-01-14 20:15:28 +01:00
Dictionary < ( uint AppID , ulong ClassID ) , ( bool Marketable , uint RealAppID , Steam . Asset . EType Type ) > descriptions = new Dictionary < ( uint AppID , ulong ClassID ) , ( bool Marketable , uint RealAppID , Steam . Asset . EType Type ) > ( ) ;
2018-06-27 21:18:54 +02:00
2019-01-10 22:33:07 +01:00
foreach ( KeyValue description in response [ "descriptions" ] . Children ) {
2019-01-11 01:51:38 +01:00
uint appID = description [ "appid" ] . AsUnsignedInteger ( ) ;
2019-01-10 22:33:07 +01:00
2019-01-11 01:51:38 +01:00
if ( appID = = 0 ) {
Bot . ArchiLogger . LogNullError ( nameof ( appID ) ) ;
2018-12-15 00:27:15 +01:00
2018-06-27 21:18:54 +02:00
return null ;
}
2016-04-20 21:27:57 +02:00
2019-01-11 01:51:38 +01:00
ulong classID = description [ "classid" ] . AsUnsignedLong ( ) ;
2018-12-15 00:27:15 +01:00
2019-01-11 01:51:38 +01:00
if ( classID = = 0 ) {
Bot . ArchiLogger . LogNullError ( nameof ( classID ) ) ;
2015-10-25 06:16:50 +01:00
2019-01-11 01:51:38 +01:00
return null ;
2019-01-10 22:33:07 +01:00
}
2018-12-15 00:27:15 +01:00
2019-01-11 02:50:36 +01:00
if ( descriptions . ContainsKey ( ( appID , classID ) ) ) {
2019-01-11 01:51:38 +01:00
continue ;
2019-01-10 22:33:07 +01:00
}
2017-02-08 14:35:01 +01:00
2019-01-14 20:15:28 +01:00
bool marketable = description [ "marketable" ] . AsBoolean ( ) ;
2019-01-11 01:51:38 +01:00
uint realAppID = 0 ;
2019-01-10 22:33:07 +01:00
Steam . Asset . EType type = Steam . Asset . EType . Unknown ;
2018-12-15 00:27:15 +01:00
2019-01-11 01:51:38 +01:00
if ( appID = = Steam . Asset . SteamAppID ) {
string hashName = description [ "market_hash_name" ] . Value ;
2017-01-11 15:22:00 +01:00
2019-01-11 01:51:38 +01:00
if ( ! string . IsNullOrEmpty ( hashName ) ) {
realAppID = GetAppIDFromMarketHashName ( hashName ) ;
}
string descriptionType = description [ "type" ] . Value ;
if ( ! string . IsNullOrEmpty ( descriptionType ) ) {
type = GetItemType ( descriptionType ) ;
}
2019-01-14 20:15:28 +01:00
}
2018-12-15 00:27:15 +01:00
2019-01-14 20:15:28 +01:00
descriptions [ ( appID , classID ) ] = ( marketable , realAppID , type ) ;
2016-06-04 22:02:38 +02:00
}
2019-01-10 22:33:07 +01:00
HashSet < Steam . TradeOffer > result = new HashSet < Steam . TradeOffer > ( ) ;
2016-06-04 22:02:38 +02:00
2019-01-10 22:33:07 +01:00
foreach ( KeyValue trade in response [ "trade_offers_received" ] . Children ) {
Steam . TradeOffer . ETradeOfferState state = trade [ "trade_offer_state" ] . AsEnum < Steam . TradeOffer . ETradeOfferState > ( ) ;
2016-06-04 22:02:38 +02:00
2019-01-10 22:33:07 +01:00
if ( state = = Steam . TradeOffer . ETradeOfferState . Unknown ) {
Bot . ArchiLogger . LogNullError ( nameof ( state ) ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return null ;
}
2016-03-29 14:33:05 +02:00
2019-01-10 22:33:07 +01:00
if ( state ! = Steam . TradeOffer . ETradeOfferState . Active ) {
continue ;
}
2016-04-22 17:51:13 +02:00
2019-01-10 22:33:07 +01:00
ulong tradeOfferID = trade [ "tradeofferid" ] . AsUnsignedLong ( ) ;
2016-04-21 00:51:22 +02:00
2019-01-10 22:33:07 +01:00
if ( tradeOfferID = = 0 ) {
Bot . ArchiLogger . LogNullError ( nameof ( tradeOfferID ) ) ;
2016-04-21 00:51:22 +02:00
2019-01-10 22:33:07 +01:00
return null ;
}
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
uint otherSteamID3 = trade [ "accountid_other" ] . AsUnsignedInteger ( ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
if ( otherSteamID3 = = 0 ) {
Bot . ArchiLogger . LogNullError ( nameof ( otherSteamID3 ) ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return null ;
2017-04-05 17:01:18 +02:00
}
2016-04-21 00:51:22 +02:00
2019-01-10 22:33:07 +01:00
Steam . TradeOffer tradeOffer = new Steam . TradeOffer ( tradeOfferID , otherSteamID3 , state ) ;
2016-04-21 02:32:36 +02:00
2019-01-10 22:33:07 +01:00
List < KeyValue > itemsToGive = trade [ "items_to_give" ] . Children ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
if ( itemsToGive . Count > 0 ) {
if ( ! ParseItems ( descriptions , itemsToGive , tradeOffer . ItemsToGive ) ) {
Bot . ArchiLogger . LogGenericError ( string . Format ( Strings . ErrorParsingObject , nameof ( itemsToGive ) ) ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return null ;
}
}
2016-04-21 02:32:36 +02:00
2019-01-10 22:33:07 +01:00
List < KeyValue > itemsToReceive = trade [ "items_to_receive" ] . Children ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
if ( itemsToReceive . Count > 0 ) {
if ( ! ParseItems ( descriptions , itemsToReceive , tradeOffer . ItemsToReceive ) ) {
Bot . ArchiLogger . LogGenericError ( string . Format ( Strings . ErrorParsingObject , nameof ( itemsToReceive ) ) ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return null ;
}
}
result . Add ( tradeOffer ) ;
2016-11-24 07:32:16 +01:00
}
2016-04-21 02:32:36 +02:00
2019-01-10 22:33:07 +01:00
return result ;
}
2016-06-10 01:16:05 +02:00
2019-01-10 23:44:32 +01:00
[ItemCanBeNull]
2019-01-10 22:33:07 +01:00
internal async Task < HashSet < uint > > GetAppList ( ) {
KeyValue response = null ;
2016-06-10 01:16:05 +02:00
2019-01-10 22:33:07 +01:00
for ( byte i = 0 ; ( i < WebBrowser . MaxTries ) & & ( response = = null ) ; i + + ) {
using ( dynamic iSteamApps = WebAPI . GetAsyncInterface ( ISteamApps ) ) {
iSteamApps . Timeout = WebBrowser . Timeout ;
2016-06-08 13:01:41 +02:00
2019-01-10 22:33:07 +01:00
try {
response = await WebLimitRequest (
WebAPI . DefaultBaseAddress . Host ,
2016-04-21 02:32:36 +02:00
2019-01-10 22:33:07 +01:00
// ReSharper disable once AccessToDisposedClosure
async ( ) = > await iSteamApps . GetAppList2 ( )
) . ConfigureAwait ( false ) ;
} catch ( TaskCanceledException e ) {
Bot . ArchiLogger . LogGenericDebuggingException ( e ) ;
} catch ( Exception e ) {
Bot . ArchiLogger . LogGenericWarningException ( e ) ;
}
2016-04-21 00:51:22 +02:00
}
2016-11-24 07:32:16 +01:00
}
2016-04-21 00:51:22 +02:00
2019-01-10 22:33:07 +01:00
if ( response = = null ) {
Bot . ArchiLogger . LogGenericWarning ( string . Format ( Strings . ErrorRequestFailedTooManyTimes , WebBrowser . MaxTries ) ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return null ;
}
2016-04-21 00:51:22 +02:00
2019-01-10 22:33:07 +01:00
List < KeyValue > apps = response [ "apps" ] . Children ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
if ( ( apps = = null ) | | ( apps . Count = = 0 ) ) {
Bot . ArchiLogger . LogNullError ( nameof ( apps ) ) ;
return null ;
2016-11-24 07:32:16 +01:00
}
2016-02-22 18:34:45 +01:00
2019-01-10 22:33:07 +01:00
HashSet < uint > result = new HashSet < uint > ( apps . Count ) ;
2016-04-21 00:51:22 +02:00
2019-01-10 22:33:07 +01:00
foreach ( KeyValue app in apps ) {
uint appID = app [ "appid" ] . AsUnsignedInteger ( ) ;
2016-10-01 00:25:15 +02:00
2019-01-10 22:33:07 +01:00
if ( appID = = 0 ) {
Bot . ArchiLogger . LogNullError ( nameof ( appID ) ) ;
2016-04-21 00:51:22 +02:00
2019-01-10 22:33:07 +01:00
return null ;
Fix latest Steam notifications fuckup
Initially the issue was observed in #697, but that itself wasn't exactly what was fixed here, as multiple evaluation of the same trade is still wanted scenario.
The real issue was reported in http://steamcommunity.com/groups/ascfarm/discussions/1/2425614539578192287/
In a huge TL;DR, Steam is now sending trades notification each time something fetches current trade offers, be it ASF, the user, or some other script.
This will lead to possible ASF trade loop, as we'll get wanted notification about new trades, fetch them, leave some trades untouched, get new notification about trades and so on.
Initially I wanted to fix this in dirty way by just ignoring any extra notifications that happened since API call until 5 extra seconds after we were done with entire parsing, but I found much better solution - Steam actually includes extra info about amount of trades/items in notification (makes sense, since Steam client displays that info too). We can make use of that info and simply ignore any extra notification that results in same or smaller count.
Thanks to that we didn't only add a decent workaround for this recent Steam fuckup, but we also improved internal ASF code that will no longer schedule extra parsing if we accepted/rejected only some of the trades, making me happy with the actual solution.
2017-11-18 17:20:24 +01:00
}
2017-09-18 16:40:01 +02:00
2019-01-10 22:33:07 +01:00
result . Add ( appID ) ;
Fix latest Steam notifications fuckup
Initially the issue was observed in #697, but that itself wasn't exactly what was fixed here, as multiple evaluation of the same trade is still wanted scenario.
The real issue was reported in http://steamcommunity.com/groups/ascfarm/discussions/1/2425614539578192287/
In a huge TL;DR, Steam is now sending trades notification each time something fetches current trade offers, be it ASF, the user, or some other script.
This will lead to possible ASF trade loop, as we'll get wanted notification about new trades, fetch them, leave some trades untouched, get new notification about trades and so on.
Initially I wanted to fix this in dirty way by just ignoring any extra notifications that happened since API call until 5 extra seconds after we were done with entire parsing, but I found much better solution - Steam actually includes extra info about amount of trades/items in notification (makes sense, since Steam client displays that info too). We can make use of that info and simply ignore any extra notification that results in same or smaller count.
Thanks to that we didn't only add a decent workaround for this recent Steam fuckup, but we also improved internal ASF code that will no longer schedule extra parsing if we accepted/rejected only some of the trades, making me happy with the actual solution.
2017-11-18 17:20:24 +01:00
}
2017-09-18 16:40:01 +02:00
2019-01-10 22:33:07 +01:00
return result ;
}
2017-09-18 16:45:31 +02:00
2019-01-10 22:33:07 +01:00
internal async Task < HtmlDocument > GetBadgePage ( byte page ) {
if ( page = = 0 ) {
Bot . ArchiLogger . LogNullError ( nameof ( page ) ) ;
Fix latest Steam notifications fuckup
Initially the issue was observed in #697, but that itself wasn't exactly what was fixed here, as multiple evaluation of the same trade is still wanted scenario.
The real issue was reported in http://steamcommunity.com/groups/ascfarm/discussions/1/2425614539578192287/
In a huge TL;DR, Steam is now sending trades notification each time something fetches current trade offers, be it ASF, the user, or some other script.
This will lead to possible ASF trade loop, as we'll get wanted notification about new trades, fetch them, leave some trades untouched, get new notification about trades and so on.
Initially I wanted to fix this in dirty way by just ignoring any extra notifications that happened since API call until 5 extra seconds after we were done with entire parsing, but I found much better solution - Steam actually includes extra info about amount of trades/items in notification (makes sense, since Steam client displays that info too). We can make use of that info and simply ignore any extra notification that results in same or smaller count.
Thanks to that we didn't only add a decent workaround for this recent Steam fuckup, but we also improved internal ASF code that will no longer schedule extra parsing if we accepted/rejected only some of the trades, making me happy with the actual solution.
2017-11-18 17:20:24 +01:00
2019-01-10 22:33:07 +01:00
return null ;
Enhance InventoryLimiterDelay
Gifts/Login limiter is actually working decent, because response is nearly instant and fast enough to not worry about it in long-run.
With inventory things are entirely different, as inventory fetching might take even a very long time, and while fetching one inventory, we might already run out of our delay and start fetching another one.
This is not a big functionality-wise, as it's nothing new for ASF to parse multiple inventories concurrently, but Steam Community actually counts number of requests, and our inventory function might ask for multiple pages during execution, which could quickly lead to a situation of 10+ ongoing inventory requests being sent concurrently for too many accounts at once, as we can't predict not only how long the request will be handled, but also how many sub-requests we will do across one.
This means that for optimal performance in terms of rate-limiting, we must limit ASF to one inventory request at a time, with mandatory InventoryLimiterDelay before asking for another one.
This can degrade performance of previously fast !loot requests on multiple accounts at once (especially with bigger inventories), but it will also decrease significantly a chance of getting rate-limited and requests failing.
2017-04-08 04:47:38 +02:00
}
2016-04-21 00:51:22 +02:00
2019-01-10 22:33:07 +01:00
string request = "/my/badges?l=english&p=" + page ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return await UrlGetToHtmlDocumentWithSession ( SteamCommunityURL , request , false ) . ConfigureAwait ( false ) ;
2017-03-13 09:40:08 +01:00
}
2019-01-10 23:44:32 +01:00
[ItemCanBeNull]
2019-01-10 22:33:07 +01:00
internal async Task < Steam . ConfirmationDetails > GetConfirmationDetails ( string deviceID , string confirmationHash , uint time , MobileAuthenticator . Confirmation confirmation ) {
if ( string . IsNullOrEmpty ( deviceID ) | | string . IsNullOrEmpty ( confirmationHash ) | | ( time = = 0 ) | | ( confirmation = = null ) ) {
Bot . ArchiLogger . LogNullError ( nameof ( deviceID ) + " || " + nameof ( confirmationHash ) + " || " + nameof ( time ) + " || " + nameof ( confirmation ) ) ;
2016-05-30 01:57:06 +02:00
2019-01-10 22:33:07 +01:00
return null ;
}
2018-06-11 21:40:52 +02:00
2019-01-10 22:33:07 +01:00
if ( SteamID = = 0 ) {
2019-01-14 21:50:23 +01:00
for ( byte i = 0 ; ( i < ASF . GlobalConfig . ConnectionTimeout ) & & ( SteamID = = 0 ) & & Bot . IsConnectedAndLoggedOn ; i + + ) {
2019-01-10 22:33:07 +01:00
await Task . Delay ( 1000 ) . ConfigureAwait ( false ) ;
}
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
if ( SteamID = = 0 ) {
Bot . ArchiLogger . LogGenericWarning ( Strings . WarningFailed ) ;
return null ;
}
2016-01-13 11:17:58 +02:00
}
2016-01-14 01:30:12 +01:00
2019-01-10 22:33:07 +01:00
string request = "/mobileconf/details/" + confirmation . ID + "?a=" + SteamID + "&k=" + WebUtility . UrlEncode ( confirmationHash ) + "&l=english&m=android&p=" + WebUtility . UrlEncode ( deviceID ) + "&t=" + time + "&tag=conf" ;
Steam . ConfirmationDetails response = await UrlGetToJsonObjectWithSession < Steam . ConfirmationDetails > ( SteamCommunityURL , request ) . ConfigureAwait ( false ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
if ( response ? . Success ! = true ) {
2018-12-06 18:06:44 +03:00
return null ;
}
2019-01-10 22:33:07 +01:00
response . Confirmation = confirmation ;
2016-11-24 07:32:16 +01:00
2019-01-10 22:33:07 +01:00
return response ;
}
2016-11-24 07:32:16 +01:00
2019-01-10 22:33:07 +01:00
internal async Task < HtmlDocument > GetConfirmations ( string deviceID , string confirmationHash , uint time ) {
if ( string . IsNullOrEmpty ( deviceID ) | | string . IsNullOrEmpty ( confirmationHash ) | | ( time = = 0 ) ) {
Bot . ArchiLogger . LogNullError ( nameof ( deviceID ) + " || " + nameof ( confirmationHash ) + " || " + nameof ( time ) ) ;
2018-12-15 00:27:15 +01:00
2018-12-06 18:06:44 +03:00
return null ;
}
2019-01-10 22:33:07 +01:00
if ( SteamID = = 0 ) {
2019-01-14 21:50:23 +01:00
for ( byte i = 0 ; ( i < ASF . GlobalConfig . ConnectionTimeout ) & & ( SteamID = = 0 ) & & Bot . IsConnectedAndLoggedOn ; i + + ) {
2019-01-10 22:33:07 +01:00
await Task . Delay ( 1000 ) . ConfigureAwait ( false ) ;
}
if ( SteamID = = 0 ) {
Bot . ArchiLogger . LogGenericWarning ( Strings . WarningFailed ) ;
return null ;
}
2018-12-06 18:06:44 +03:00
}
2019-01-10 22:33:07 +01:00
string request = "/mobileconf/conf?a=" + SteamID + "&k=" + WebUtility . UrlEncode ( confirmationHash ) + "&l=english&m=android&p=" + WebUtility . UrlEncode ( deviceID ) + "&t=" + time + "&tag=conf" ;
return await UrlGetToHtmlDocumentWithSession ( SteamCommunityURL , request ) . ConfigureAwait ( false ) ;
}
2018-12-15 00:27:15 +01:00
2019-01-10 23:44:32 +01:00
[ItemCanBeNull]
2019-01-10 22:33:07 +01:00
internal async Task < HashSet < ulong > > GetDigitalGiftCards ( ) {
const string request = "/gifts" ;
HtmlDocument response = await UrlGetToHtmlDocumentWithSession ( SteamStoreURL , request ) . ConfigureAwait ( false ) ;
HtmlNodeCollection htmlNodes = response ? . DocumentNode . SelectNodes ( "//div[@class='pending_gift']/div[starts-with(@id, 'pending_gift_')][count(div[@class='pending_giftcard_leftcol']) > 0]/@id" ) ;
if ( htmlNodes = = null ) {
2018-12-06 18:06:44 +03:00
return null ;
}
2019-01-10 22:33:07 +01:00
HashSet < ulong > results = new HashSet < ulong > ( ) ;
foreach ( string giftCardIDText in htmlNodes . Select ( node = > node . GetAttributeValue ( "id" , null ) ) ) {
if ( string . IsNullOrEmpty ( giftCardIDText ) ) {
Bot . ArchiLogger . LogNullError ( nameof ( giftCardIDText ) ) ;
2018-12-15 00:27:15 +01:00
2018-12-06 18:06:44 +03:00
return null ;
}
2019-01-10 22:33:07 +01:00
if ( giftCardIDText . Length < = 13 ) {
Bot . ArchiLogger . LogGenericError ( string . Format ( Strings . ErrorIsInvalid , nameof ( giftCardIDText ) ) ) ;
return null ;
2018-12-06 18:06:44 +03:00
}
2019-01-10 22:33:07 +01:00
if ( ! ulong . TryParse ( giftCardIDText . Substring ( 13 ) , out ulong giftCardID ) | | ( giftCardID = = 0 ) ) {
Bot . ArchiLogger . LogGenericError ( string . Format ( Strings . ErrorParsingObject , nameof ( giftCardID ) ) ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return null ;
}
results . Add ( giftCardID ) ;
2018-12-06 18:06:44 +03:00
}
2019-01-10 22:33:07 +01:00
return results ;
2016-01-13 11:17:58 +02:00
}
2019-01-10 22:33:07 +01:00
internal async Task < HtmlDocument > GetDiscoveryQueuePage ( ) {
const string request = "/explore?l=english" ;
2016-01-13 11:17:58 +02:00
2019-01-10 22:33:07 +01:00
return await UrlGetToHtmlDocumentWithSession ( SteamStoreURL , request ) . ConfigureAwait ( false ) ;
}
2016-04-20 21:27:57 +02:00
2019-01-10 23:44:32 +01:00
[ItemCanBeNull]
2019-01-10 22:33:07 +01:00
internal async Task < HashSet < ulong > > GetFamilySharingSteamIDs ( ) {
const string request = "/account/managedevices?l=english" ;
HtmlDocument htmlDocument = await UrlGetToHtmlDocumentWithSession ( SteamStoreURL , request ) . ConfigureAwait ( false ) ;
Implement ETradingPreferences.MatchActively
This will probably need a lot more tests, tweaking and bugfixing, but basic logic is:
- MatchActively added to TradingPreferences with value of 16
- User must also use SteamTradeMatcher, can't use MatchEverything
- User must have statistics enabled and be eligible for being listed (no requirement of having 100 items minimum)
Once all requirements are passed, statistics module will communicate with the listing and fetch match everything bots:
- The matching will start in 1h since ASF start and will repeat every day (right now it starts in 1 minute to aid debugging).
- Each matching is composed of up to 10 rounds maximum.
- In each round ASF will fetch our inventory and inventory of listed bots in order to find MatchableTypes items to be matched. If match is found, offer is being sent and confirmed automatically.
- Each set (composition of item type + appID it's from) can be matched in a single round only once, this is to minimize "items no longer available" as much as possible and also avoid a need to wait for each bot to react before sending all trades.
- Round ends when we try to match a total of 20 bots, or we hit no items to match in consecutive 10 tries with 10 different bots.
- If last round resulted in at least a single trade being sent, next round starts within 5 minutes since last one, otherwise matching ends and repeats the next day.
We'll see how it works in practice, expect a lot of follow-up commits, unless I won't have anything to fix or improve.
2018-11-29 18:35:58 +01:00
2019-01-10 22:33:07 +01:00
HtmlNodeCollection htmlNodes = htmlDocument ? . DocumentNode . SelectNodes ( "(//table[@class='accountTable'])[last()]//a/@data-miniprofile" ) ;
2016-01-24 00:00:27 +01:00
2019-01-10 22:33:07 +01:00
if ( htmlNodes = = null ) {
return null ; // OK, no authorized steamIDs
Implement ETradingPreferences.MatchActively
This will probably need a lot more tests, tweaking and bugfixing, but basic logic is:
- MatchActively added to TradingPreferences with value of 16
- User must also use SteamTradeMatcher, can't use MatchEverything
- User must have statistics enabled and be eligible for being listed (no requirement of having 100 items minimum)
Once all requirements are passed, statistics module will communicate with the listing and fetch match everything bots:
- The matching will start in 1h since ASF start and will repeat every day (right now it starts in 1 minute to aid debugging).
- Each matching is composed of up to 10 rounds maximum.
- In each round ASF will fetch our inventory and inventory of listed bots in order to find MatchableTypes items to be matched. If match is found, offer is being sent and confirmed automatically.
- Each set (composition of item type + appID it's from) can be matched in a single round only once, this is to minimize "items no longer available" as much as possible and also avoid a need to wait for each bot to react before sending all trades.
- Round ends when we try to match a total of 20 bots, or we hit no items to match in consecutive 10 tries with 10 different bots.
- If last round resulted in at least a single trade being sent, next round starts within 5 minutes since last one, otherwise matching ends and repeats the next day.
We'll see how it works in practice, expect a lot of follow-up commits, unless I won't have anything to fix or improve.
2018-11-29 18:35:58 +01:00
}
2019-01-10 22:33:07 +01:00
HashSet < ulong > result = new HashSet < ulong > ( ) ;
Implement ETradingPreferences.MatchActively
This will probably need a lot more tests, tweaking and bugfixing, but basic logic is:
- MatchActively added to TradingPreferences with value of 16
- User must also use SteamTradeMatcher, can't use MatchEverything
- User must have statistics enabled and be eligible for being listed (no requirement of having 100 items minimum)
Once all requirements are passed, statistics module will communicate with the listing and fetch match everything bots:
- The matching will start in 1h since ASF start and will repeat every day (right now it starts in 1 minute to aid debugging).
- Each matching is composed of up to 10 rounds maximum.
- In each round ASF will fetch our inventory and inventory of listed bots in order to find MatchableTypes items to be matched. If match is found, offer is being sent and confirmed automatically.
- Each set (composition of item type + appID it's from) can be matched in a single round only once, this is to minimize "items no longer available" as much as possible and also avoid a need to wait for each bot to react before sending all trades.
- Round ends when we try to match a total of 20 bots, or we hit no items to match in consecutive 10 tries with 10 different bots.
- If last round resulted in at least a single trade being sent, next round starts within 5 minutes since last one, otherwise matching ends and repeats the next day.
We'll see how it works in practice, expect a lot of follow-up commits, unless I won't have anything to fix or improve.
2018-11-29 18:35:58 +01:00
2019-01-10 22:33:07 +01:00
foreach ( string miniProfile in htmlNodes . Select ( htmlNode = > htmlNode . GetAttributeValue ( "data-miniprofile" , null ) ) ) {
if ( string . IsNullOrEmpty ( miniProfile ) ) {
Bot . ArchiLogger . LogNullError ( nameof ( miniProfile ) ) ;
2016-01-22 10:13:02 +01:00
2019-01-10 22:33:07 +01:00
return null ;
Implement ETradingPreferences.MatchActively
This will probably need a lot more tests, tweaking and bugfixing, but basic logic is:
- MatchActively added to TradingPreferences with value of 16
- User must also use SteamTradeMatcher, can't use MatchEverything
- User must have statistics enabled and be eligible for being listed (no requirement of having 100 items minimum)
Once all requirements are passed, statistics module will communicate with the listing and fetch match everything bots:
- The matching will start in 1h since ASF start and will repeat every day (right now it starts in 1 minute to aid debugging).
- Each matching is composed of up to 10 rounds maximum.
- In each round ASF will fetch our inventory and inventory of listed bots in order to find MatchableTypes items to be matched. If match is found, offer is being sent and confirmed automatically.
- Each set (composition of item type + appID it's from) can be matched in a single round only once, this is to minimize "items no longer available" as much as possible and also avoid a need to wait for each bot to react before sending all trades.
- Round ends when we try to match a total of 20 bots, or we hit no items to match in consecutive 10 tries with 10 different bots.
- If last round resulted in at least a single trade being sent, next round starts within 5 minutes since last one, otherwise matching ends and repeats the next day.
We'll see how it works in practice, expect a lot of follow-up commits, unless I won't have anything to fix or improve.
2018-11-29 18:35:58 +01:00
}
2018-10-13 00:17:45 +02:00
2019-01-10 22:33:07 +01:00
if ( ! uint . TryParse ( miniProfile , out uint steamID3 ) | | ( steamID3 = = 0 ) ) {
Bot . ArchiLogger . LogNullError ( nameof ( steamID3 ) ) ;
2018-10-13 00:17:45 +02:00
2019-01-10 22:33:07 +01:00
return null ;
2018-06-16 07:47:07 +02:00
}
2018-10-13 00:17:45 +02:00
2019-01-10 22:33:07 +01:00
ulong steamID = new SteamID ( steamID3 , EUniverse . Public , EAccountType . Individual ) ;
result . Add ( steamID ) ;
2016-01-13 11:17:58 +02:00
}
2016-01-14 01:30:12 +01:00
2019-01-10 22:33:07 +01:00
return result ;
2017-07-23 03:43:20 +02:00
}
2019-01-10 22:33:07 +01:00
internal async Task < HtmlDocument > GetGameCardsPage ( ulong appID ) {
if ( appID = = 0 ) {
Bot . ArchiLogger . LogNullError ( nameof ( appID ) ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return null ;
2017-07-23 03:43:20 +02:00
}
2019-01-10 22:33:07 +01:00
string request = "/my/gamecards/" + appID + "?l=english" ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return await UrlGetToHtmlDocumentWithSession ( SteamCommunityURL , request , false ) . ConfigureAwait ( false ) ;
}
2018-05-20 14:42:48 +02:00
2019-01-10 23:44:32 +01:00
[ItemCanBeNull]
2019-01-10 22:33:07 +01:00
internal async Task < Dictionary < uint , string > > GetMyOwnedGames ( ) {
const string request = "/my/games?l=english&xml=1" ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
XmlDocument response = await UrlGetToXmlDocumentWithSession ( SteamCommunityURL , request , false ) . ConfigureAwait ( false ) ;
2016-01-14 02:48:56 +01:00
2019-01-10 22:33:07 +01:00
XmlNodeList xmlNodeList = response ? . SelectNodes ( "gamesList/games/game" ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
if ( ( xmlNodeList = = null ) | | ( xmlNodeList . Count = = 0 ) ) {
return null ;
}
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
Dictionary < uint , string > result = new Dictionary < uint , string > ( xmlNodeList . Count ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
foreach ( XmlNode xmlNode in xmlNodeList ) {
XmlNode appNode = xmlNode . SelectSingleNode ( "appID" ) ;
2016-03-29 14:33:05 +02:00
2019-01-10 22:33:07 +01:00
if ( appNode = = null ) {
Bot . ArchiLogger . LogNullError ( nameof ( appNode ) ) ;
2016-02-22 18:34:45 +01:00
2019-01-10 22:33:07 +01:00
return null ;
}
2016-03-29 14:33:05 +02:00
2019-01-10 22:33:07 +01:00
if ( ! uint . TryParse ( appNode . InnerText , out uint appID ) | | ( appID = = 0 ) ) {
Bot . ArchiLogger . LogNullError ( nameof ( appID ) ) ;
2016-02-28 03:40:59 +01:00
2019-01-10 22:33:07 +01:00
return null ;
}
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
XmlNode nameNode = xmlNode . SelectSingleNode ( "name" ) ;
2018-05-12 20:21:52 +02:00
2019-01-10 22:33:07 +01:00
if ( nameNode = = null ) {
Bot . ArchiLogger . LogNullError ( nameof ( nameNode ) ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return null ;
}
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
result [ appID ] = nameNode . InnerText ;
2018-05-20 14:42:48 +02:00
}
2019-01-10 22:33:07 +01:00
return result ;
2018-05-12 20:21:52 +02:00
}
2019-01-10 23:44:32 +01:00
[ItemCanBeNull]
2019-01-10 22:33:07 +01:00
internal async Task < Dictionary < uint , string > > GetOwnedGames ( ulong steamID ) {
if ( steamID = = 0 ) {
Bot . ArchiLogger . LogNullError ( nameof ( steamID ) ) ;
return null ;
2018-11-06 21:41:59 +01:00
}
2019-01-10 22:33:07 +01:00
( bool success , string steamApiKey ) = await CachedApiKey . GetValue ( ) . ConfigureAwait ( false ) ;
2018-11-06 21:41:59 +01:00
2019-01-10 22:33:07 +01:00
if ( ! success | | string . IsNullOrEmpty ( steamApiKey ) ) {
return null ;
}
2018-11-06 21:41:59 +01:00
2019-01-10 22:33:07 +01:00
KeyValue response = null ;
2019-01-03 01:06:08 +01:00
2019-01-10 22:33:07 +01:00
for ( byte i = 0 ; ( i < WebBrowser . MaxTries ) & & ( response = = null ) ; i + + ) {
using ( dynamic iPlayerService = WebAPI . GetAsyncInterface ( IPlayerService , steamApiKey ) ) {
iPlayerService . Timeout = WebBrowser . Timeout ;
2018-11-06 21:41:59 +01:00
2019-01-10 22:33:07 +01:00
try {
response = await WebLimitRequest (
WebAPI . DefaultBaseAddress . Host ,
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
// ReSharper disable once AccessToDisposedClosure
async ( ) = > await iPlayerService . GetOwnedGames (
include_appinfo : 1 ,
steamid : steamID
)
) . ConfigureAwait ( false ) ;
} catch ( TaskCanceledException e ) {
Bot . ArchiLogger . LogGenericDebuggingException ( e ) ;
} catch ( Exception e ) {
Bot . ArchiLogger . LogGenericWarningException ( e ) ;
}
2018-11-06 21:41:59 +01:00
}
2019-01-10 22:33:07 +01:00
}
2018-11-06 21:41:59 +01:00
2019-01-10 22:33:07 +01:00
if ( response = = null ) {
Bot . ArchiLogger . LogGenericWarning ( string . Format ( Strings . ErrorRequestFailedTooManyTimes , WebBrowser . MaxTries ) ) ;
2018-11-06 21:41:59 +01:00
2019-01-10 22:33:07 +01:00
return null ;
}
2018-11-06 21:41:59 +01:00
2019-01-10 22:33:07 +01:00
List < KeyValue > games = response [ "games" ] . Children ;
2018-11-06 21:41:59 +01:00
2019-01-10 22:33:07 +01:00
Dictionary < uint , string > result = new Dictionary < uint , string > ( games . Count ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
foreach ( KeyValue game in games ) {
uint appID = game [ "appid" ] . AsUnsignedInteger ( ) ;
2018-11-06 21:41:59 +01:00
2019-01-10 22:33:07 +01:00
if ( appID = = 0 ) {
Bot . ArchiLogger . LogNullError ( nameof ( appID ) ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return null ;
}
result [ appID ] = game [ "name" ] . Value ;
2018-02-17 03:57:09 +01:00
}
2019-01-10 22:33:07 +01:00
return result ;
2018-02-17 03:57:09 +01:00
}
2019-01-10 22:33:07 +01:00
internal async Task < uint > GetServerTime ( ) {
KeyValue response = null ;
2016-11-24 07:32:16 +01:00
2019-01-10 22:33:07 +01:00
for ( byte i = 0 ; ( i < WebBrowser . MaxTries ) & & ( response = = null ) ; i + + ) {
using ( dynamic iTwoFactorService = WebAPI . GetAsyncInterface ( ITwoFactorService ) ) {
iTwoFactorService . Timeout = WebBrowser . Timeout ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
try {
response = await WebLimitRequest (
WebAPI . DefaultBaseAddress . Host ,
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
// ReSharper disable once AccessToDisposedClosure
async ( ) = > await iTwoFactorService . QueryTime ( method : WebRequestMethods . Http . Post )
) . ConfigureAwait ( false ) ;
} catch ( TaskCanceledException e ) {
Bot . ArchiLogger . LogGenericDebuggingException ( e ) ;
} catch ( Exception e ) {
Bot . ArchiLogger . LogGenericWarningException ( e ) ;
}
2016-11-24 07:32:16 +01:00
}
2019-01-10 22:33:07 +01:00
}
2016-11-24 07:32:16 +01:00
2019-01-10 22:33:07 +01:00
if ( response = = null ) {
Bot . ArchiLogger . LogGenericWarning ( string . Format ( Strings . ErrorRequestFailedTooManyTimes , WebBrowser . MaxTries ) ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return 0 ;
}
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
uint result = response [ "server_time" ] . AsUnsignedInteger ( ) ;
2016-11-24 07:32:16 +01:00
2019-01-10 22:33:07 +01:00
if ( result = = 0 ) {
Bot . ArchiLogger . LogNullError ( nameof ( result ) ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return 0 ;
}
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return result ;
}
2016-11-24 07:32:16 +01:00
2019-01-10 22:33:07 +01:00
internal async Task < byte? > GetTradeHoldDurationForTrade ( ulong tradeID ) {
if ( tradeID = = 0 ) {
Bot . ArchiLogger . LogNullError ( nameof ( tradeID ) ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return null ;
}
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
string request = "/tradeoffer/" + tradeID + "?l=english" ;
2016-11-24 07:32:16 +01:00
2019-01-10 22:33:07 +01:00
HtmlDocument htmlDocument = await UrlGetToHtmlDocumentWithSession ( SteamCommunityURL , request ) . ConfigureAwait ( false ) ;
2016-11-24 07:32:16 +01:00
2019-01-10 22:33:07 +01:00
HtmlNode htmlNode = htmlDocument ? . DocumentNode . SelectSingleNode ( "//div[@class='pagecontent']/script" ) ;
2016-11-24 07:32:16 +01:00
2019-01-10 22:33:07 +01:00
if ( htmlNode = = null ) {
// Trade can be no longer valid
return null ;
2016-11-24 07:32:16 +01:00
}
2019-01-10 22:33:07 +01:00
string text = htmlNode . InnerText ;
2016-11-24 07:32:16 +01:00
2019-01-10 22:33:07 +01:00
if ( string . IsNullOrEmpty ( text ) ) {
Bot . ArchiLogger . LogNullError ( nameof ( text ) ) ;
return null ;
2016-03-27 23:07:00 +02:00
}
2019-01-10 22:33:07 +01:00
int index = text . IndexOf ( "g_daysTheirEscrow = " , StringComparison . Ordinal ) ;
2018-02-16 17:53:33 +01:00
2019-01-10 22:33:07 +01:00
if ( index < 0 ) {
Bot . ArchiLogger . LogNullError ( nameof ( index ) ) ;
2016-03-27 23:07:00 +02:00
2019-01-10 22:33:07 +01:00
return null ;
}
2018-02-16 16:48:55 +01:00
2019-01-10 22:33:07 +01:00
index + = 20 ;
text = text . Substring ( index ) ;
2016-04-24 23:32:23 +02:00
2019-01-10 22:33:07 +01:00
index = text . IndexOf ( ';' ) ;
2018-02-16 17:53:33 +01:00
2019-01-10 22:33:07 +01:00
if ( index < 0 ) {
Bot . ArchiLogger . LogNullError ( nameof ( index ) ) ;
2018-02-16 17:53:33 +01:00
2019-01-10 22:33:07 +01:00
return null ;
2016-03-27 23:07:00 +02:00
}
2019-01-10 22:33:07 +01:00
text = text . Substring ( 0 , index ) ;
2017-01-11 15:22:00 +01:00
2019-01-10 22:33:07 +01:00
if ( ! byte . TryParse ( text , out byte result ) ) {
Bot . ArchiLogger . LogNullError ( nameof ( result ) ) ;
2017-01-11 15:22:00 +01:00
2019-01-10 22:33:07 +01:00
return null ;
}
return result ;
2017-01-11 15:22:00 +01:00
}
2019-01-10 22:33:07 +01:00
internal async Task < byte? > GetTradeHoldDurationForUser ( ulong steamID , string tradeToken = null ) {
if ( steamID = = 0 ) {
Bot . ArchiLogger . LogNullError ( nameof ( steamID ) ) ;
2018-12-12 22:19:52 +01:00
2019-01-10 22:33:07 +01:00
return null ;
}
2018-12-12 22:19:52 +01:00
2019-01-10 22:33:07 +01:00
( bool success , string steamApiKey ) = await CachedApiKey . GetValue ( ) . ConfigureAwait ( false ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
if ( ! success | | string . IsNullOrEmpty ( steamApiKey ) ) {
return null ;
}
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
KeyValue response = null ;
2018-12-12 22:19:52 +01:00
2019-01-10 22:33:07 +01:00
for ( byte i = 0 ; ( i < WebBrowser . MaxTries ) & & ( response = = null ) ; i + + ) {
using ( dynamic iEconService = WebAPI . GetAsyncInterface ( IEconService , steamApiKey ) ) {
iEconService . Timeout = WebBrowser . Timeout ;
2018-12-12 22:24:05 +01:00
2019-01-10 22:33:07 +01:00
try {
response = await WebLimitRequest (
WebAPI . DefaultBaseAddress . Host ,
2018-12-12 22:19:52 +01:00
2019-01-10 22:33:07 +01:00
// ReSharper disable once AccessToDisposedClosure
async ( ) = > await iEconService . GetTradeHoldDurations (
steamid_target : steamID ,
trade_offer_access_token : tradeToken ? ? "" // TODO: Change me once https://github.com/SteamRE/SteamKit/pull/522 is merged
)
) . ConfigureAwait ( false ) ;
} catch ( TaskCanceledException e ) {
Bot . ArchiLogger . LogGenericDebuggingException ( e ) ;
} catch ( Exception e ) {
Bot . ArchiLogger . LogGenericWarningException ( e ) ;
2018-12-12 22:24:05 +01:00
}
2019-01-10 22:33:07 +01:00
}
}
2018-12-12 22:24:05 +01:00
2019-01-10 22:33:07 +01:00
if ( response = = null ) {
Bot . ArchiLogger . LogGenericWarning ( string . Format ( Strings . ErrorRequestFailedTooManyTimes , WebBrowser . MaxTries ) ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return null ;
}
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
uint resultInSeconds = response [ "their_escrow" ] [ "escrow_end_duration_seconds" ] . AsUnsignedInteger ( uint . MaxValue ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
if ( resultInSeconds = = uint . MaxValue ) {
Bot . ArchiLogger . LogNullError ( nameof ( resultInSeconds ) ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return null ;
2018-12-12 22:19:52 +01:00
}
2019-01-10 22:33:07 +01:00
return resultInSeconds = = 0 ? ( byte ) 0 : ( byte ) ( resultInSeconds / 86400 ) ;
2018-12-12 22:19:52 +01:00
}
2019-01-10 22:33:07 +01:00
internal async Task < bool? > HandleConfirmation ( string deviceID , string confirmationHash , uint time , ulong confirmationID , ulong confirmationKey , bool accept ) {
if ( string . IsNullOrEmpty ( deviceID ) | | string . IsNullOrEmpty ( confirmationHash ) | | ( time = = 0 ) | | ( confirmationID = = 0 ) | | ( confirmationKey = = 0 ) ) {
Bot . ArchiLogger . LogNullError ( nameof ( deviceID ) + " || " + nameof ( confirmationHash ) + " || " + nameof ( time ) + " || " + nameof ( confirmationID ) + " || " + nameof ( confirmationKey ) ) ;
2018-12-12 22:19:52 +01:00
2019-01-10 22:33:07 +01:00
return null ;
2018-12-12 22:19:52 +01:00
}
2019-01-10 22:33:07 +01:00
if ( SteamID = = 0 ) {
2019-01-14 21:50:23 +01:00
for ( byte i = 0 ; ( i < ASF . GlobalConfig . ConnectionTimeout ) & & ( SteamID = = 0 ) & & Bot . IsConnectedAndLoggedOn ; i + + ) {
2019-01-10 22:33:07 +01:00
await Task . Delay ( 1000 ) . ConfigureAwait ( false ) ;
}
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
if ( SteamID = = 0 ) {
Bot . ArchiLogger . LogGenericWarning ( Strings . WarningFailed ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return null ;
}
2018-12-12 22:19:52 +01:00
}
2019-01-10 22:33:07 +01:00
string request = "/mobileconf/ajaxop?a=" + SteamID + "&cid=" + confirmationID + "&ck=" + confirmationKey + "&k=" + WebUtility . UrlEncode ( confirmationHash ) + "&l=english&m=android&op=" + ( accept ? "allow" : "cancel" ) + "&p=" + WebUtility . UrlEncode ( deviceID ) + "&t=" + time + "&tag=conf" ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
Steam . BooleanResponse response = await UrlGetToJsonObjectWithSession < Steam . BooleanResponse > ( SteamCommunityURL , request ) . ConfigureAwait ( false ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return response ? . Success ;
}
2018-12-12 22:19:52 +01:00
2019-01-10 22:33:07 +01:00
internal async Task < bool? > HandleConfirmations ( string deviceID , string confirmationHash , uint time , IReadOnlyCollection < MobileAuthenticator . Confirmation > confirmations , bool accept ) {
if ( string . IsNullOrEmpty ( deviceID ) | | string . IsNullOrEmpty ( confirmationHash ) | | ( time = = 0 ) | | ( confirmations = = null ) | | ( confirmations . Count = = 0 ) ) {
Bot . ArchiLogger . LogNullError ( nameof ( deviceID ) + " || " + nameof ( confirmationHash ) + " || " + nameof ( time ) + " || " + nameof ( confirmations ) ) ;
2018-12-12 22:19:52 +01:00
2019-01-10 22:33:07 +01:00
return null ;
}
2018-12-12 22:19:52 +01:00
2019-01-10 22:33:07 +01:00
if ( SteamID = = 0 ) {
2019-01-14 21:50:23 +01:00
for ( byte i = 0 ; ( i < ASF . GlobalConfig . ConnectionTimeout ) & & ( SteamID = = 0 ) & & Bot . IsConnectedAndLoggedOn ; i + + ) {
2019-01-10 22:33:07 +01:00
await Task . Delay ( 1000 ) . ConfigureAwait ( false ) ;
}
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
if ( SteamID = = 0 ) {
Bot . ArchiLogger . LogGenericWarning ( Strings . WarningFailed ) ;
return null ;
}
2018-12-12 22:19:52 +01:00
}
2019-01-10 22:33:07 +01:00
const string request = "/mobileconf/multiajaxop" ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
// Extra entry for sessionID
List < KeyValuePair < string , string > > data = new List < KeyValuePair < string , string > > ( 8 + confirmations . Count * 2 ) {
new KeyValuePair < string , string > ( "a" , SteamID . ToString ( ) ) ,
new KeyValuePair < string , string > ( "k" , confirmationHash ) ,
new KeyValuePair < string , string > ( "m" , "android" ) ,
new KeyValuePair < string , string > ( "op" , accept ? "allow" : "cancel" ) ,
new KeyValuePair < string , string > ( "p" , deviceID ) ,
new KeyValuePair < string , string > ( "t" , time . ToString ( ) ) ,
new KeyValuePair < string , string > ( "tag" , "conf" )
} ;
2018-12-12 22:19:52 +01:00
2019-01-10 22:33:07 +01:00
foreach ( MobileAuthenticator . Confirmation confirmation in confirmations ) {
data . Add ( new KeyValuePair < string , string > ( "cid[]" , confirmation . ID . ToString ( ) ) ) ;
data . Add ( new KeyValuePair < string , string > ( "ck[]" , confirmation . Key . ToString ( ) ) ) ;
}
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
Steam . BooleanResponse response = await UrlPostToJsonObjectWithSession < Steam . BooleanResponse > ( SteamCommunityURL , request , data ) . ConfigureAwait ( false ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return response ? . Success ;
}
2018-12-22 14:45:36 +01:00
2019-01-10 22:33:07 +01:00
internal async Task < bool > HasPublicInventory ( ) {
( bool success , bool hasPublicInventory ) = await CachedPublicInventory . GetValue ( ) . ConfigureAwait ( false ) ;
2018-12-22 14:45:36 +01:00
2019-01-10 22:33:07 +01:00
return success & & hasPublicInventory ;
}
2018-12-22 14:45:36 +01:00
2019-01-10 22:33:07 +01:00
internal async Task < bool > HasValidApiKey ( ) {
( bool success , string steamApiKey ) = await CachedApiKey . GetValue ( ) . ConfigureAwait ( false ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return success & & ! string . IsNullOrEmpty ( steamApiKey ) ;
2018-12-12 22:19:52 +01:00
}
2019-01-10 22:33:07 +01:00
internal async Task < bool > Init ( ulong steamID , EUniverse universe , string webAPIUserNonce , string parentalCode = null ) {
if ( ( steamID = = 0 ) | | ( universe = = EUniverse . Invalid ) | | string . IsNullOrEmpty ( webAPIUserNonce ) ) {
Bot . ArchiLogger . LogNullError ( nameof ( steamID ) + " || " + nameof ( universe ) + " || " + nameof ( webAPIUserNonce ) ) ;
2018-12-15 00:27:15 +01:00
2016-05-30 01:57:06 +02:00
return false ;
}
2019-01-10 22:33:07 +01:00
string sessionID = Convert . ToBase64String ( Encoding . UTF8 . GetBytes ( steamID . ToString ( ) ) ) ;
2016-04-24 23:32:23 +02:00
2019-01-10 22:33:07 +01:00
// Generate an AES session key
byte [ ] sessionKey = CryptoHelper . GenerateRandomBlock ( 32 ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
// RSA encrypt it with the public key for the universe we're on
byte [ ] encryptedSessionKey ;
using ( RSACrypto rsa = new RSACrypto ( KeyDictionary . GetPublicKey ( universe ) ) ) {
encryptedSessionKey = rsa . Encrypt ( sessionKey ) ;
2017-01-17 18:25:38 +01:00
}
2019-01-10 22:33:07 +01:00
// Copy our login key
byte [ ] loginKey = new byte [ webAPIUserNonce . Length ] ;
Array . Copy ( Encoding . ASCII . GetBytes ( webAPIUserNonce ) , loginKey , webAPIUserNonce . Length ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
// AES encrypt the login key with our session key
byte [ ] encryptedLoginKey = CryptoHelper . SymmetricEncrypt ( loginKey , sessionKey ) ;
2017-01-17 18:25:38 +01:00
2019-01-10 22:33:07 +01:00
// Do the magic
Bot . ArchiLogger . LogGenericInfo ( string . Format ( Strings . LoggingIn , ISteamUserAuth ) ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
KeyValue response ;
2017-01-17 18:25:38 +01:00
2019-01-10 22:33:07 +01:00
// We do not use usual retry pattern here as webAPIUserNonce is valid only for a single request
// Even during timeout, webAPIUserNonce is most likely already invalid
// Instead, the caller is supposed to ask for new webAPIUserNonce and call Init() again on failure
using ( dynamic iSteamUserAuth = WebAPI . GetAsyncInterface ( ISteamUserAuth ) ) {
iSteamUserAuth . Timeout = WebBrowser . Timeout ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
try {
response = await WebLimitRequest (
WebAPI . DefaultBaseAddress . Host ,
2017-01-17 18:25:38 +01:00
2019-01-10 22:33:07 +01:00
// ReSharper disable once AccessToDisposedClosure
async ( ) = > await iSteamUserAuth . AuthenticateUser (
encrypted_loginkey : Encoding . ASCII . GetString ( WebUtility . UrlEncodeToBytes ( encryptedLoginKey , 0 , encryptedLoginKey . Length ) ) ,
method : WebRequestMethods . Http . Post ,
sessionkey : Encoding . ASCII . GetString ( WebUtility . UrlEncodeToBytes ( encryptedSessionKey , 0 , encryptedSessionKey . Length ) ) ,
steamid : steamID
)
) . ConfigureAwait ( false ) ;
} catch ( TaskCanceledException e ) {
Bot . ArchiLogger . LogGenericDebuggingException ( e ) ;
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
2019-01-10 22:33:07 +01:00
return false ;
} catch ( Exception e ) {
Bot . ArchiLogger . LogGenericWarningException ( e ) ;
return false ;
}
}
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
if ( response = = null ) {
2018-05-12 20:21:52 +02:00
return false ;
}
2019-01-10 22:33:07 +01:00
string steamLogin = response [ "token" ] . Value ;
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
2019-01-10 22:33:07 +01:00
if ( string . IsNullOrEmpty ( steamLogin ) ) {
Bot . ArchiLogger . LogNullError ( nameof ( steamLogin ) ) ;
2018-12-15 00:27:15 +01:00
2018-05-12 20:21:52 +02:00
return false ;
}
2019-01-10 22:33:07 +01:00
string steamLoginSecure = response [ "tokensecure" ] . Value ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
if ( string . IsNullOrEmpty ( steamLoginSecure ) ) {
Bot . ArchiLogger . LogNullError ( nameof ( steamLoginSecure ) ) ;
return false ;
2018-05-12 20:21:52 +02:00
}
2019-01-10 22:33:07 +01:00
WebBrowser . CookieContainer . Add ( new Cookie ( "sessionid" , sessionID , "/" , "." + SteamCommunityHost ) ) ;
WebBrowser . CookieContainer . Add ( new Cookie ( "sessionid" , sessionID , "/" , "." + SteamStoreHost ) ) ;
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
2019-01-10 22:33:07 +01:00
WebBrowser . CookieContainer . Add ( new Cookie ( "steamLogin" , steamLogin , "/" , "." + SteamCommunityHost ) ) ;
WebBrowser . CookieContainer . Add ( new Cookie ( "steamLogin" , steamLogin , "/" , "." + SteamStoreHost ) ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
WebBrowser . CookieContainer . Add ( new Cookie ( "steamLoginSecure" , steamLoginSecure , "/" , "." + SteamCommunityHost ) ) ;
WebBrowser . CookieContainer . Add ( new Cookie ( "steamLoginSecure" , steamLoginSecure , "/" , "." + SteamStoreHost ) ) ;
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
2019-01-10 22:33:07 +01:00
Bot . ArchiLogger . LogGenericInfo ( Strings . Success ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
// Unlock Steam Parental if needed
if ( ( parentalCode ! = null ) & & ( parentalCode . Length = = 4 ) ) {
if ( ! await UnlockParentalAccount ( parentalCode ) . ConfigureAwait ( false ) ) {
return false ;
}
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
}
2019-01-10 22:33:07 +01:00
SteamID = steamID ;
LastSessionCheck = LastSessionRefresh = DateTime . UtcNow ;
2018-11-06 21:41:59 +01:00
2019-01-10 22:33:07 +01:00
return true ;
}
2018-11-10 07:23:04 +01:00
2019-01-10 22:33:07 +01:00
internal async Task < bool > JoinGroup ( ulong groupID ) {
if ( groupID = = 0 ) {
Bot . ArchiLogger . LogNullError ( nameof ( groupID ) ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return false ;
2018-11-06 21:41:59 +01:00
}
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
2019-01-10 22:33:07 +01:00
string request = "/gid/" + groupID ;
2018-05-20 14:42:48 +02:00
2019-01-10 22:33:07 +01:00
// Extra entry for sessionID
Dictionary < string , string > data = new Dictionary < string , string > ( 2 ) { { "action" , "join" } } ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return await UrlPostWithSession ( SteamCommunityURL , request , data , session : ESession . CamelCase ) . ConfigureAwait ( false ) ;
}
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
2019-01-10 22:33:07 +01:00
internal async Task MarkInventory ( ) {
// We aim to have a maximum of 2 tasks, one already working, and one waiting in the queue
// This way we can call this function as many times as needed e.g. because of Steam events
lock ( InventorySemaphore ) {
if ( MarkingInventoryScheduled ) {
return ;
}
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
MarkingInventoryScheduled = true ;
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
}
2019-01-10 22:33:07 +01:00
await InventorySemaphore . WaitAsync ( ) . ConfigureAwait ( false ) ;
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
2019-01-10 22:33:07 +01:00
try {
lock ( InventorySemaphore ) {
MarkingInventoryScheduled = false ;
}
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
const string request = "/my/inventory" ;
await UrlHeadWithSession ( SteamCommunityURL , request , false ) . ConfigureAwait ( false ) ;
} finally {
2019-01-14 21:50:23 +01:00
if ( ASF . GlobalConfig . InventoryLimiterDelay = = 0 ) {
2019-01-10 22:33:07 +01:00
InventorySemaphore . Release ( ) ;
} else {
Utilities . InBackground (
async ( ) = > {
2019-01-14 21:50:23 +01:00
await Task . Delay ( ASF . GlobalConfig . InventoryLimiterDelay * 1000 ) . ConfigureAwait ( false ) ;
2019-01-10 22:33:07 +01:00
InventorySemaphore . Release ( ) ;
}
) ;
}
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
}
2019-01-10 22:33:07 +01:00
}
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
2019-01-10 22:33:07 +01:00
internal async Task < bool > MarkSentTrades ( ) {
const string request = "/my/tradeoffers/sent" ;
2018-05-12 20:21:52 +02:00
2019-01-10 22:33:07 +01:00
return await UrlHeadWithSession ( SteamCommunityURL , request , false ) . ConfigureAwait ( false ) ;
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
}
2019-01-10 22:33:07 +01:00
internal void OnDisconnected ( ) {
SteamID = 0 ;
Utilities . InBackground ( CachedApiKey . Reset ) ;
Utilities . InBackground ( CachedPublicInventory . Reset ) ;
}
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
internal void OnVanityURLChanged ( string vanityURL = null ) = > VanityURL = string . IsNullOrEmpty ( vanityURL ) ? null : vanityURL ;
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
2019-01-10 22:33:07 +01:00
internal async Task < ( EResult Result , EPurchaseResultDetail ? PurchaseResult ) ? > RedeemWalletKey ( string key ) {
if ( string . IsNullOrEmpty ( key ) ) {
Bot . ArchiLogger . LogNullError ( nameof ( key ) ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return null ;
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
}
2019-01-10 22:33:07 +01:00
// ASF should redeem wallet key only in case of existing wallet
if ( Bot . WalletCurrency = = ECurrencyCode . Invalid ) {
Bot . ArchiLogger . LogNullError ( nameof ( Bot . WalletCurrency ) ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return null ;
2018-11-06 21:41:59 +01:00
}
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
2019-01-10 22:33:07 +01:00
const string requestValidateCode = "/account/validatewalletcode" ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
// Extra entry for sessionID
Dictionary < string , string > data = new Dictionary < string , string > ( 2 ) { { "wallet_code" , key } } ;
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
2019-01-10 22:33:07 +01:00
Steam . RedeemWalletResponse responseValidateCode = await UrlPostToJsonObjectWithSession < Steam . RedeemWalletResponse > ( SteamStoreURL , requestValidateCode , data ) . ConfigureAwait ( false ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
if ( responseValidateCode = = null ) {
return null ;
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
}
2019-01-10 22:33:07 +01:00
// We can not trust EResult response, because it is OK even in the case of error, so changing it to Fail in this case
if ( ( responseValidateCode . Result ! = EResult . OK ) | | ( responseValidateCode . PurchaseResultDetail ! = EPurchaseResultDetail . NoDetail ) ) {
return ( responseValidateCode . Result = = EResult . OK ? EResult . Fail : responseValidateCode . Result , responseValidateCode . PurchaseResultDetail ) ;
}
if ( responseValidateCode . KeyDetails = = null ) {
Bot . ArchiLogger . LogNullError ( nameof ( responseValidateCode . KeyDetails ) ) ;
2018-12-15 00:27:15 +01:00
2018-05-12 20:21:52 +02:00
return null ;
}
2019-01-10 22:33:07 +01:00
if ( responseValidateCode . WalletCurrencyCode ! = responseValidateCode . KeyDetails . CurrencyCode ) {
const string requestCheckFunds = "/account/createwalletandcheckfunds" ;
Steam . EResultResponse responseCheckFunds = await UrlPostToJsonObjectWithSession < Steam . EResultResponse > ( SteamStoreURL , requestCheckFunds , data ) . ConfigureAwait ( false ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
if ( responseCheckFunds = = null ) {
return null ;
}
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
2019-01-10 22:33:07 +01:00
if ( responseCheckFunds . Result ! = EResult . OK ) {
return ( responseCheckFunds . Result , null ) ;
}
}
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
2019-01-10 22:33:07 +01:00
const string requestConfirmRedeem = "/account/confirmredeemwalletcode" ;
Steam . RedeemWalletResponse responseConfirmRedeem = await UrlPostToJsonObjectWithSession < Steam . RedeemWalletResponse > ( SteamStoreURL , requestConfirmRedeem , data ) . ConfigureAwait ( false ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
if ( responseConfirmRedeem = = null ) {
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
return null ;
}
2019-01-10 22:33:07 +01:00
// Same, returning OK EResult only if PurchaseResultDetail is NoDetail (no error occured)
return ( ( responseConfirmRedeem . PurchaseResultDetail = = EPurchaseResultDetail . NoDetail ) & & ( responseConfirmRedeem . Result = = EResult . OK ) ? responseConfirmRedeem . Result : EResult . Fail , responseConfirmRedeem . PurchaseResultDetail ) ;
}
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
internal async Task < ( bool Success , HashSet < ulong > MobileTradeOfferIDs ) > SendTradeOffer ( ulong partnerID , IReadOnlyCollection < Steam . Asset > itemsToGive = null , IReadOnlyCollection < Steam . Asset > itemsToReceive = null , string token = null , bool forcedSingleOffer = false ) {
if ( ( partnerID = = 0 ) | | ( ( ( itemsToGive = = null ) | | ( itemsToGive . Count = = 0 ) ) & & ( ( itemsToReceive = = null ) | | ( itemsToReceive . Count = = 0 ) ) ) ) {
Bot . ArchiLogger . LogNullError ( nameof ( partnerID ) + " || (" + nameof ( itemsToGive ) + " && " + nameof ( itemsToReceive ) + ")" ) ;
return ( false , null ) ;
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
}
2019-01-10 22:33:07 +01:00
Steam . TradeOfferSendRequest singleTrade = new Steam . TradeOfferSendRequest ( ) ;
HashSet < Steam . TradeOfferSendRequest > trades = new HashSet < Steam . TradeOfferSendRequest > { singleTrade } ;
2018-11-06 21:41:59 +01:00
2019-01-10 22:33:07 +01:00
if ( itemsToGive ! = null ) {
foreach ( Steam . Asset itemToGive in itemsToGive ) {
if ( ! forcedSingleOffer & & ( singleTrade . ItemsToGive . Assets . Count + singleTrade . ItemsToReceive . Assets . Count > = Trading . MaxItemsPerTrade ) ) {
if ( trades . Count > = Trading . MaxTradesPerAccount ) {
break ;
}
2018-11-10 07:23:04 +01:00
2019-01-10 22:33:07 +01:00
singleTrade = new Steam . TradeOfferSendRequest ( ) ;
trades . Add ( singleTrade ) ;
}
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
singleTrade . ItemsToGive . Assets . Add ( itemToGive ) ;
2018-11-06 21:41:59 +01:00
}
}
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
2019-01-10 22:33:07 +01:00
if ( itemsToReceive ! = null ) {
foreach ( Steam . Asset itemToReceive in itemsToReceive ) {
if ( ! forcedSingleOffer & & ( singleTrade . ItemsToGive . Assets . Count + singleTrade . ItemsToReceive . Assets . Count > = Trading . MaxItemsPerTrade ) ) {
if ( trades . Count > = Trading . MaxTradesPerAccount ) {
break ;
}
2018-05-20 14:42:48 +02:00
2019-01-10 22:33:07 +01:00
singleTrade = new Steam . TradeOfferSendRequest ( ) ;
trades . Add ( singleTrade ) ;
}
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
singleTrade . ItemsToReceive . Assets . Add ( itemToReceive ) ;
2018-05-20 14:42:48 +02:00
}
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
}
2019-01-10 22:33:07 +01:00
const string request = "/tradeoffer/new/send" ;
const string referer = SteamCommunityURL + "/tradeoffer/new" ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
// Extra entry for sessionID
Dictionary < string , string > data = new Dictionary < string , string > ( 6 ) {
{ "partner" , partnerID . ToString ( ) } ,
{ "serverid" , "1" } ,
{ "trade_offer_create_params" , string . IsNullOrEmpty ( token ) ? "" : new JObject { { "trade_offer_access_token" , token } } . ToString ( Formatting . None ) } ,
{ "tradeoffermessage" , "Sent by " + SharedInfo . PublicIdentifier + "/" + SharedInfo . Version }
} ;
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
2019-01-10 22:33:07 +01:00
HashSet < ulong > mobileTradeOfferIDs = new HashSet < ulong > ( ) ;
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
2019-01-10 22:33:07 +01:00
foreach ( Steam . TradeOfferSendRequest trade in trades ) {
data [ "json_tradeoffer" ] = JsonConvert . SerializeObject ( trade ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
Steam . TradeOfferSendResponse response = await UrlPostToJsonObjectWithSession < Steam . TradeOfferSendResponse > ( SteamCommunityURL , request , data , referer ) . ConfigureAwait ( false ) ;
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
2019-01-10 22:33:07 +01:00
if ( response = = null ) {
return ( false , mobileTradeOfferIDs ) ;
}
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
if ( response . RequiresMobileConfirmation ) {
mobileTradeOfferIDs . Add ( response . TradeOfferID ) ;
}
2018-05-12 20:21:52 +02:00
}
2019-01-10 22:33:07 +01:00
return ( true , mobileTradeOfferIDs ) ;
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
}
2019-01-10 22:33:07 +01:00
internal async Task < bool > UnpackBooster ( uint appID , ulong itemID ) {
if ( ( appID = = 0 ) | | ( itemID = = 0 ) ) {
Bot . ArchiLogger . LogNullError ( nameof ( appID ) + " || " + nameof ( itemID ) ) ;
2018-12-15 00:27:15 +01:00
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
return false ;
}
2019-01-10 22:33:07 +01:00
string profileURL = await GetAbsoluteProfileURL ( ) . ConfigureAwait ( false ) ;
if ( string . IsNullOrEmpty ( profileURL ) ) {
Bot . ArchiLogger . LogGenericWarning ( Strings . WarningFailed ) ;
2018-12-15 00:27:15 +01:00
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
return false ;
}
2019-01-10 22:33:07 +01:00
string request = profileURL + "/ajaxunpackbooster" ;
2018-11-06 21:41:59 +01:00
2019-01-10 22:33:07 +01:00
// Extra entry for sessionID
Dictionary < string , string > data = new Dictionary < string , string > ( 3 ) {
{ "appid" , appID . ToString ( ) } ,
{ "communityitemid" , itemID . ToString ( ) }
} ;
2018-11-10 07:23:04 +01:00
2019-01-10 22:33:07 +01:00
Steam . EResultResponse response = await UrlPostToJsonObjectWithSession < Steam . EResultResponse > ( SteamCommunityURL , request , data ) . ConfigureAwait ( false ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return response ? . Result = = EResult . OK ;
}
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
2019-01-10 22:33:07 +01:00
private async Task < ( ESteamApiKeyState State , string Key ) > GetApiKeyState ( ) {
const string request = "/dev/apikey?l=english" ;
HtmlDocument htmlDocument = await UrlGetToHtmlDocumentWithSession ( SteamCommunityURL , request ) . ConfigureAwait ( false ) ;
2018-05-20 14:42:48 +02:00
2019-01-10 22:33:07 +01:00
HtmlNode titleNode = htmlDocument ? . DocumentNode . SelectSingleNode ( "//div[@id='mainContents']/h2" ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
if ( titleNode = = null ) {
return ( ESteamApiKeyState . Timeout , null ) ;
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
}
2019-01-10 22:33:07 +01:00
string title = titleNode . InnerText ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
if ( string . IsNullOrEmpty ( title ) ) {
Bot . ArchiLogger . LogNullError ( nameof ( title ) ) ;
return ( ESteamApiKeyState . Error , null ) ;
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
}
2019-01-10 22:33:07 +01:00
if ( title . Contains ( "Access Denied" ) | | title . Contains ( "Validated email address required" ) ) {
return ( ESteamApiKeyState . AccessDenied , null ) ;
}
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
2019-01-10 22:33:07 +01:00
HtmlNode htmlNode = htmlDocument . DocumentNode . SelectSingleNode ( "//div[@id='bodyContents_ex']/p" ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
if ( htmlNode = = null ) {
Bot . ArchiLogger . LogNullError ( nameof ( htmlNode ) ) ;
return ( ESteamApiKeyState . Error , null ) ;
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
}
2019-01-10 22:33:07 +01:00
string text = htmlNode . InnerText ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
if ( string . IsNullOrEmpty ( text ) ) {
Bot . ArchiLogger . LogNullError ( nameof ( text ) ) ;
return ( ESteamApiKeyState . Error , null ) ;
2018-05-12 20:21:52 +02:00
}
2019-01-10 22:33:07 +01:00
if ( text . Contains ( "Registering for a Steam Web API Key" ) ) {
return ( ESteamApiKeyState . NotRegisteredYet , null ) ;
}
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
2019-01-10 22:33:07 +01:00
int keyIndex = text . IndexOf ( "Key: " , StringComparison . Ordinal ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
if ( keyIndex < 0 ) {
Bot . ArchiLogger . LogNullError ( nameof ( keyIndex ) ) ;
return ( ESteamApiKeyState . Error , null ) ;
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
}
2019-01-10 22:33:07 +01:00
keyIndex + = 5 ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
if ( text . Length < = keyIndex ) {
Bot . ArchiLogger . LogNullError ( nameof ( text ) ) ;
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
2019-01-10 22:33:07 +01:00
return ( ESteamApiKeyState . Error , null ) ;
}
2018-11-06 21:41:59 +01:00
2019-01-10 22:33:07 +01:00
text = text . Substring ( keyIndex ) ;
2018-11-10 07:23:04 +01:00
2019-01-10 22:33:07 +01:00
if ( ( text . Length ! = 32 ) | | ! Utilities . IsValidHexadecimalString ( text ) ) {
Bot . ArchiLogger . LogNullError ( nameof ( text ) ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return ( ESteamApiKeyState . Error , null ) ;
2018-11-06 21:41:59 +01:00
}
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
2019-01-10 22:33:07 +01:00
return ( ESteamApiKeyState . Registered , text ) ;
}
2018-05-20 14:42:48 +02:00
2019-01-10 22:33:07 +01:00
private static uint GetAppIDFromMarketHashName ( string hashName ) {
if ( string . IsNullOrEmpty ( hashName ) ) {
ASF . ArchiLogger . LogNullError ( nameof ( hashName ) ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return 0 ;
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
}
2019-01-10 22:33:07 +01:00
int index = hashName . IndexOf ( '-' ) ;
2018-02-16 17:53:33 +01:00
2019-01-11 00:51:11 +01:00
if ( index < = 0 ) {
2019-01-11 02:29:22 +01:00
ASF . ArchiLogger . LogGenericError ( string . Format ( Strings . WarningUnknownValuePleaseReport , nameof ( hashName ) , hashName ) ) ;
2019-01-11 00:51:11 +01:00
return 0 ;
}
if ( ! uint . TryParse ( hashName . Substring ( 0 , index ) , out uint appID ) | | ( appID = = 0 ) ) {
ASF . ArchiLogger . LogNullError ( nameof ( appID ) ) ;
return 0 ;
}
return appID ;
2019-01-10 22:33:07 +01:00
}
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
private static Steam . Asset . EType GetItemType ( string name ) {
if ( string . IsNullOrEmpty ( name ) ) {
ASF . ArchiLogger . LogNullError ( nameof ( name ) ) ;
2018-02-16 17:53:33 +01:00
2019-01-10 22:33:07 +01:00
return Steam . Asset . EType . Unknown ;
}
2018-03-06 19:15:49 +01:00
2019-01-10 22:33:07 +01:00
switch ( name ) {
case "Booster Pack" :
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return Steam . Asset . EType . BoosterPack ;
case "Steam Gems" :
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return Steam . Asset . EType . SteamGems ;
default :
2018-12-15 00:27:15 +01:00
2019-01-11 04:10:42 +01:00
if ( name . EndsWith ( "Consumable" , StringComparison . Ordinal ) ) {
return Steam . Asset . EType . Consumable ;
}
2019-01-10 22:33:07 +01:00
if ( name . EndsWith ( "Emoticon" , StringComparison . Ordinal ) ) {
return Steam . Asset . EType . Emoticon ;
}
2018-03-06 19:15:49 +01:00
2019-01-10 22:33:07 +01:00
if ( name . EndsWith ( "Foil Trading Card" , StringComparison . Ordinal ) ) {
return Steam . Asset . EType . FoilTradingCard ;
}
if ( name . EndsWith ( "Profile Background" , StringComparison . Ordinal ) ) {
return Steam . Asset . EType . ProfileBackground ;
}
2019-01-11 01:14:09 +01:00
if ( name . EndsWith ( "Sale Item" , StringComparison . Ordinal ) ) {
return Steam . Asset . EType . SaleItem ;
}
2019-01-11 00:51:11 +01:00
if ( name . EndsWith ( "Trading Card" , StringComparison . Ordinal ) ) {
return Steam . Asset . EType . TradingCard ;
}
2019-01-11 02:29:22 +01:00
ASF . ArchiLogger . LogGenericError ( string . Format ( Strings . WarningUnknownValuePleaseReport , nameof ( name ) , name ) ) ;
2019-01-11 00:51:11 +01:00
return Steam . Asset . EType . Unknown ;
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
}
2019-01-10 22:33:07 +01:00
}
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
2019-01-10 22:33:07 +01:00
private async Task < bool > IsProfileUri ( Uri uri , bool waitForInitialization = true ) {
if ( uri = = null ) {
ASF . ArchiLogger . LogNullError ( nameof ( uri ) ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return false ;
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
}
2019-01-10 22:33:07 +01:00
string profileURL = await GetAbsoluteProfileURL ( waitForInitialization ) . ConfigureAwait ( false ) ;
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
2019-01-10 22:33:07 +01:00
if ( string . IsNullOrEmpty ( profileURL ) ) {
2018-03-09 15:43:25 +01:00
Bot . ArchiLogger . LogGenericWarning ( Strings . WarningFailed ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return false ;
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
}
2019-01-10 22:33:07 +01:00
return uri . AbsolutePath . Equals ( profileURL ) ;
}
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
private async Task < bool? > IsSessionExpired ( ) {
if ( DateTime . UtcNow < LastSessionCheck . AddSeconds ( MinSessionValidityInSeconds ) ) {
return LastSessionCheck ! = LastSessionRefresh ;
2018-05-12 20:21:52 +02:00
}
2019-01-10 22:33:07 +01:00
await SessionSemaphore . WaitAsync ( ) . ConfigureAwait ( false ) ;
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
2019-01-10 22:33:07 +01:00
try {
if ( DateTime . UtcNow < LastSessionCheck . AddSeconds ( MinSessionValidityInSeconds ) ) {
return LastSessionCheck ! = LastSessionRefresh ;
}
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
// Choosing proper URL to check against is actually much harder than it initially looks like, we must abide by several rules to make this function as lightweight and reliable as possible
// We should prefer to use Steam store, as the community is much more unstable and broken, plus majority of our requests get there anyway, so load-balancing with store makes much more sense. It also has a higher priority than the community, so all eventual issues should be fixed there first
// The URL must be fast enough to render, as this function will be called reasonably often, and every extra delay adds up. We're already making our best effort by using HEAD request, but the URL itself plays a very important role as well
// 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
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
2019-01-10 22:33:07 +01:00
const string host = SteamStoreURL ;
const string request = "/account" ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
WebBrowser . BasicResponse response = await WebLimitRequest ( host , async ( ) = > await WebBrowser . UrlHead ( host + request ) . ConfigureAwait ( false ) ) . ConfigureAwait ( false ) ;
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
2019-01-10 22:33:07 +01:00
if ( response ? . FinalUri = = null ) {
return null ;
}
2018-11-06 21:41:59 +01:00
2019-01-10 22:33:07 +01:00
bool result = IsSessionExpiredUri ( response . FinalUri ) ;
2018-11-10 07:23:04 +01:00
2019-01-10 22:33:07 +01:00
DateTime now = DateTime . UtcNow ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
if ( ! result ) {
LastSessionRefresh = now ;
2018-11-06 21:41:59 +01:00
}
2019-01-10 22:33:07 +01:00
LastSessionCheck = now ;
return result ;
} finally {
2018-11-06 21:41:59 +01:00
SessionSemaphore . Release ( ) ;
}
2019-01-10 22:33:07 +01:00
}
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
2019-01-10 22:33:07 +01:00
private static bool IsSessionExpiredUri ( Uri uri ) {
if ( uri = = null ) {
ASF . ArchiLogger . LogNullError ( nameof ( uri ) ) ;
2018-05-20 14:42:48 +02:00
2019-01-10 22:33:07 +01:00
return false ;
}
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return uri . AbsolutePath . StartsWith ( "/login" , StringComparison . Ordinal ) | | uri . Host . Equals ( "lostauth" ) ;
}
2019-01-14 20:15:28 +01:00
private static bool ParseItems ( IReadOnlyDictionary < ( uint AppID , ulong ClassID ) , ( bool Marketable , uint RealAppID , Steam . Asset . EType Type ) > descriptions , IReadOnlyCollection < KeyValue > input , ICollection < Steam . Asset > output ) {
2019-01-10 22:33:07 +01:00
if ( ( descriptions = = null ) | | ( input = = null ) | | ( input . Count = = 0 ) | | ( output = = null ) ) {
ASF . ArchiLogger . LogNullError ( nameof ( descriptions ) + " || " + nameof ( input ) + " || " + nameof ( output ) ) ;
return false ;
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
}
2019-01-10 22:33:07 +01:00
foreach ( KeyValue item in input ) {
uint appID = item [ "appid" ] . AsUnsignedInteger ( ) ;
2018-02-16 17:53:33 +01:00
2019-01-10 22:33:07 +01:00
if ( appID = = 0 ) {
ASF . ArchiLogger . LogNullError ( nameof ( appID ) ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return false ;
2018-02-16 17:53:33 +01:00
}
2019-01-11 01:08:01 +01:00
uint contextID = item [ "contextid" ] . AsUnsignedInteger ( ) ;
2018-03-06 19:15:49 +01:00
2019-01-10 22:33:07 +01:00
if ( contextID = = 0 ) {
ASF . ArchiLogger . LogNullError ( nameof ( contextID ) ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return false ;
}
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
ulong classID = item [ "classid" ] . AsUnsignedLong ( ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
if ( classID = = 0 ) {
ASF . ArchiLogger . LogNullError ( nameof ( classID ) ) ;
2018-03-06 19:15:49 +01:00
2019-01-10 22:33:07 +01:00
return false ;
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
}
2019-01-10 22:33:07 +01:00
uint amount = item [ "amount" ] . AsUnsignedInteger ( ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
if ( amount = = 0 ) {
ASF . ArchiLogger . LogNullError ( nameof ( amount ) ) ;
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
2019-01-10 22:33:07 +01:00
return false ;
2018-05-12 20:21:52 +02:00
}
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
2019-01-14 20:15:28 +01:00
bool marketable = true ;
2019-01-11 01:51:38 +01:00
uint realAppID = 0 ;
2019-01-10 22:33:07 +01:00
Steam . Asset . EType type = Steam . Asset . EType . Unknown ;
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
2019-01-14 20:15:28 +01:00
if ( descriptions . TryGetValue ( ( appID , classID ) , out ( bool Marketable , uint RealAppID , Steam . Asset . EType Type ) description ) ) {
marketable = description . Marketable ;
2019-01-11 01:51:38 +01:00
realAppID = description . RealAppID ;
2019-01-10 22:33:07 +01:00
type = description . Type ;
}
2018-12-15 00:27:15 +01:00
2019-01-14 20:15:28 +01:00
Steam . Asset steamAsset = new Steam . Asset ( appID , contextID , classID , amount , marketable , realAppID , type ) ;
2019-01-10 22:33:07 +01:00
output . Add ( steamAsset ) ;
2018-05-12 20:21:52 +02:00
}
2019-01-10 22:33:07 +01:00
return true ;
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
}
2019-01-10 22:33:07 +01:00
private async Task < bool > RefreshSession ( ) {
if ( ! Bot . IsConnectedAndLoggedOn ) {
return false ;
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
}
2019-01-10 22:33:07 +01:00
DateTime now = DateTime . UtcNow ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
await SessionSemaphore . WaitAsync ( ) . ConfigureAwait ( false ) ;
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
2019-01-10 22:33:07 +01:00
try {
if ( now < LastSessionRefresh ) {
return true ;
}
2018-11-06 21:41:59 +01:00
2019-01-10 22:33:07 +01:00
if ( ! Bot . IsConnectedAndLoggedOn ) {
return false ;
}
2018-11-10 07:23:04 +01:00
2019-01-10 22:33:07 +01:00
Bot . ArchiLogger . LogGenericInfo ( Strings . RefreshingOurSession ) ;
bool result = await Bot . RefreshSession ( ) . ConfigureAwait ( false ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
if ( result ) {
LastSessionCheck = LastSessionRefresh = DateTime . UtcNow ;
}
return result ;
} finally {
2018-11-06 21:41:59 +01:00
SessionSemaphore . Release ( ) ;
}
2019-01-10 22:33:07 +01:00
}
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
2019-01-10 22:33:07 +01:00
private async Task < bool > RegisterApiKey ( ) {
const string request = "/dev/registerkey" ;
2018-05-20 14:42:48 +02:00
2019-01-10 22:33:07 +01:00
// Extra entry for sessionID
Dictionary < string , string > data = new Dictionary < string , string > ( 4 ) {
{ "agreeToTerms" , "agreed" } ,
{ "domain" , "localhost" } ,
{ "Submit" , "Register" }
} ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return await UrlPostWithSession ( SteamCommunityURL , request , data ) . ConfigureAwait ( false ) ;
}
private async Task < ( bool Success , string Result ) > ResolveApiKey ( ) {
if ( Bot . IsAccountLimited ) {
// API key is permanently unavailable for limited accounts
return ( true , null ) ;
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
}
2019-01-10 22:33:07 +01:00
( ESteamApiKeyState State , string Key ) result = await GetApiKeyState ( ) . ConfigureAwait ( false ) ;
2018-02-16 17:53:33 +01:00
2019-01-10 22:33:07 +01:00
switch ( result . State ) {
case ESteamApiKeyState . AccessDenied :
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
// We succeeded in fetching API key, but it resulted in access denied
// Return empty result, API key is unavailable permanently
return ( true , "" ) ;
case ESteamApiKeyState . NotRegisteredYet :
2018-02-16 17:53:33 +01:00
2019-01-10 22:33:07 +01:00
// We succeeded in fetching API key, and it resulted in no key registered yet
// Let's try to register a new key
if ( ! await RegisterApiKey ( ) . ConfigureAwait ( false ) ) {
// Request timed out, bad luck, we'll try again later
goto case ESteamApiKeyState . Timeout ;
}
2018-03-06 19:15:49 +01:00
2019-01-10 22:33:07 +01:00
// We should have the key ready, so let's fetch it again
result = await GetApiKeyState ( ) . ConfigureAwait ( false ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
if ( result . State = = ESteamApiKeyState . Timeout ) {
// Request timed out, bad luck, we'll try again later
goto case ESteamApiKeyState . Timeout ;
}
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
if ( result . State ! = ESteamApiKeyState . Registered ) {
// Something went wrong, report error
goto default ;
}
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
goto case ESteamApiKeyState . Registered ;
case ESteamApiKeyState . Registered :
2018-03-06 19:15:49 +01:00
2019-01-10 22:33:07 +01:00
// We succeeded in fetching API key, and it resulted in registered key
// Cache the result, this is the API key we want
return ( true , result . Key ) ;
case ESteamApiKeyState . Timeout :
2018-03-09 23:48:47 +01:00
2019-01-10 22:33:07 +01:00
// Request timed out, bad luck, we'll try again later
return ( false , null ) ;
default :
// We got an unhandled error, this should never happen
Bot . ArchiLogger . LogGenericError ( string . Format ( Strings . WarningUnknownValuePleaseReport , nameof ( result . State ) , result . State ) ) ;
return ( false , null ) ;
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
}
2019-01-10 22:33:07 +01:00
}
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
2019-01-10 22:33:07 +01:00
private async Task < ( bool Success , bool Result ) > ResolvePublicInventory ( ) {
const string request = "/my/edit/settings?l=english" ;
HtmlDocument htmlDocument = await UrlGetToHtmlDocumentWithSession ( SteamCommunityURL , request , false ) . ConfigureAwait ( false ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
if ( htmlDocument = = null ) {
return ( false , false ) ;
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
}
2019-01-10 22:33:07 +01:00
HtmlNode htmlNode = htmlDocument . DocumentNode . SelectSingleNode ( "//div[@data-component='ProfilePrivacySettings']/@data-privacysettings" ) ;
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
2019-01-10 22:33:07 +01:00
if ( htmlNode = = null ) {
Bot . ArchiLogger . LogNullError ( nameof ( htmlNode ) ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return ( false , false ) ;
2018-05-12 20:21:52 +02:00
}
2019-01-10 22:33:07 +01:00
string json = htmlNode . GetAttributeValue ( "data-privacysettings" , null ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
if ( string . IsNullOrEmpty ( json ) ) {
Bot . ArchiLogger . LogNullError ( nameof ( json ) ) ;
return ( false , false ) ;
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
}
2019-01-10 22:33:07 +01:00
// This json is encoded as html attribute, don't forget to decode it
json = WebUtility . HtmlDecode ( json ) ;
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
2019-01-10 22:33:07 +01:00
Steam . UserPrivacy userPrivacy ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
try {
userPrivacy = JsonConvert . DeserializeObject < Steam . UserPrivacy > ( json ) ;
} catch ( JsonException e ) {
Bot . ArchiLogger . LogGenericException ( e ) ;
return ( false , false ) ;
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
}
2019-01-10 22:33:07 +01:00
if ( userPrivacy = = null ) {
Bot . ArchiLogger . LogNullError ( nameof ( userPrivacy ) ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return ( false , false ) ;
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
}
2019-01-10 22:33:07 +01:00
switch ( userPrivacy . Settings . Profile ) {
case Steam . UserPrivacy . PrivacySettings . EPrivacySetting . FriendsOnly :
case Steam . UserPrivacy . PrivacySettings . EPrivacySetting . Private :
2018-11-06 21:41:59 +01:00
2019-01-10 22:33:07 +01:00
return ( true , false ) ;
case Steam . UserPrivacy . PrivacySettings . EPrivacySetting . Public :
2018-11-10 07:23:04 +01:00
2019-01-10 22:33:07 +01:00
switch ( userPrivacy . Settings . Inventory ) {
case Steam . UserPrivacy . PrivacySettings . EPrivacySetting . FriendsOnly :
case Steam . UserPrivacy . PrivacySettings . EPrivacySetting . Private :
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return ( true , false ) ;
case Steam . UserPrivacy . PrivacySettings . EPrivacySetting . Public :
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
2019-01-10 22:33:07 +01:00
return ( true , true ) ;
default :
Bot . ArchiLogger . LogGenericError ( string . Format ( Strings . WarningUnknownValuePleaseReport , nameof ( userPrivacy . Settings . Inventory ) , userPrivacy . Settings . Inventory ) ) ;
2018-05-20 14:42:48 +02:00
2019-01-10 22:33:07 +01:00
return ( false , false ) ;
}
default :
Bot . ArchiLogger . LogGenericError ( string . Format ( Strings . WarningUnknownValuePleaseReport , nameof ( userPrivacy . Settings . Profile ) , userPrivacy . Settings . Profile ) ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return ( false , false ) ;
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
}
2019-01-10 22:33:07 +01:00
}
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
2019-01-10 22:33:07 +01:00
private async Task < bool > UnlockParentalAccount ( string parentalCode ) {
if ( string . IsNullOrEmpty ( parentalCode ) ) {
Bot . ArchiLogger . LogNullError ( nameof ( parentalCode ) ) ;
2018-02-16 17:53:33 +01:00
2019-01-10 22:33:07 +01:00
return false ;
}
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
Bot . ArchiLogger . LogGenericInfo ( Strings . UnlockingParentalAccount ) ;
2018-02-16 17:53:33 +01:00
2019-01-10 22:33:07 +01:00
if ( ! await UnlockParentalAccountForService ( SteamCommunityURL , parentalCode ) . ConfigureAwait ( false ) ) {
Bot . ArchiLogger . LogGenericWarning ( Strings . WarningFailed ) ;
2018-03-06 19:15:49 +01:00
2019-01-10 22:33:07 +01:00
return false ;
}
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
if ( ! await UnlockParentalAccountForService ( SteamStoreURL , parentalCode ) . ConfigureAwait ( false ) ) {
Bot . ArchiLogger . LogGenericWarning ( Strings . WarningFailed ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return false ;
}
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
Bot . ArchiLogger . LogGenericInfo ( Strings . Success ) ;
2018-03-06 19:15:49 +01:00
2019-01-10 22:33:07 +01:00
return true ;
}
private async Task < bool > UnlockParentalAccountForService ( string serviceURL , string parentalCode , byte maxTries = WebBrowser . MaxTries ) {
if ( string . IsNullOrEmpty ( serviceURL ) | | string . IsNullOrEmpty ( parentalCode ) ) {
Bot . ArchiLogger . LogNullError ( nameof ( serviceURL ) + " || " + nameof ( parentalCode ) ) ;
return false ;
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
}
2019-01-10 22:33:07 +01:00
const string request = "/parental/ajaxunlock" ;
if ( maxTries = = 0 ) {
Bot . ArchiLogger . LogGenericWarning ( string . Format ( Strings . ErrorRequestFailedTooManyTimes , WebBrowser . MaxTries ) ) ;
Bot . ArchiLogger . LogGenericDebug ( string . Format ( Strings . ErrorFailingRequest , serviceURL + request ) ) ;
2018-12-15 00:27:15 +01:00
2018-03-09 23:48:47 +01:00
return false ;
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
}
2019-01-10 22:33:07 +01:00
Dictionary < string , string > data = new Dictionary < string , string > ( 1 ) { { "pin" , parentalCode } } ;
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
2019-01-10 22:33:07 +01:00
// This request doesn't go through UrlPostRetryWithSession as we have no access to session refresh capability (this is in fact session initialization)
WebBrowser . BasicResponse response = await WebLimitRequest ( serviceURL , async ( ) = > await WebBrowser . UrlPost ( serviceURL + request , data , serviceURL ) . ConfigureAwait ( false ) ) . ConfigureAwait ( false ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
if ( ( response = = null ) | | IsSessionExpiredUri ( response . FinalUri ) ) {
// There is no session refresh capability at this stage
2018-03-09 23:48:47 +01:00
return false ;
Rewrite entire ASF HTTP stack
Previous implementation was quite naive and assumed a lot of non-guaranteed premises. Checking if our session is valid before doing each request is very stupid, but it was needed for multi-user sessions. Next, even if we checked that our session is valid (or revalidated it if needed), we then assumed it's valid for at least X seconds, which is once again entirely false. Moreover, on top of those two issues, refreshing our session could do absolutely nothing as there is no guarantee that once-refreshes session stays like that even for our very next request.
Get rid of all of this shit and rewrite it with proper mechanism. We're making a request, if that request results in redirection to session refresh, refresh session, then repeat the same request again. Add failsafes for infinite loops, make it enough thread-safe and optimized for concurrent usage.
This commit won't only improve previously half-valid implementation, but will also greatly optimize number of requests being sent, as we won't need to check our session before each request. The entire thing should work like that since beginning.
2018-02-16 15:49:18 +01:00
}
2017-01-17 18:25:38 +01:00
2018-05-12 20:21:52 +02:00
// Under special brain-damaged circumstances, Steam might just return our own profile as a response to the request, for absolutely no reason whatsoever - just try again in this case
2019-01-10 22:33:07 +01:00
if ( await IsProfileUri ( response . FinalUri , false ) . ConfigureAwait ( false ) ) {
2018-05-12 20:21:52 +02:00
Bot . ArchiLogger . LogGenericDebug ( string . Format ( Strings . WarningWorkaroundTriggered , nameof ( IsProfileUri ) ) ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
return await UnlockParentalAccountForService ( serviceURL , parentalCode , - - maxTries ) . ConfigureAwait ( false ) ;
2018-05-12 20:21:52 +02:00
}
return true ;
2016-02-22 18:34:45 +01:00
}
2017-01-11 15:22:00 +01:00
2018-04-13 09:17:27 +02:00
private static async Task < T > WebLimitRequest < T > ( string service , Func < Task < T > > function ) {
if ( string . IsNullOrEmpty ( service ) | | ( function = = null ) ) {
ASF . ArchiLogger . LogNullError ( nameof ( service ) + " || " + nameof ( function ) ) ;
2018-12-15 00:27:15 +01:00
2018-04-13 09:17:27 +02:00
return default ;
}
2019-01-14 21:50:23 +01:00
if ( ASF . GlobalConfig . WebLimiterDelay = = 0 ) {
2018-04-13 09:17:27 +02:00
return await function ( ) . ConfigureAwait ( false ) ;
}
2018-04-13 09:49:54 +02:00
if ( ! WebLimitingSemaphores . TryGetValue ( service , out ( SemaphoreSlim RateLimitingSemaphore , SemaphoreSlim OpenConnectionsSemaphore ) limiters ) ) {
2019-01-10 22:33:07 +01:00
ASF . ArchiLogger . LogGenericWarning ( string . Format ( Strings . WarningUnknownValuePleaseReport , nameof ( service ) , service ) ) ;
2018-12-15 00:27:15 +01:00
2019-01-10 22:33:07 +01:00
if ( ! WebLimitingSemaphores . TryGetValue ( nameof ( ArchiWebHandler ) , out limiters ) ) {
ASF . ArchiLogger . LogNullError ( nameof ( limiters ) ) ;
return await function ( ) . ConfigureAwait ( false ) ;
}
2018-04-13 09:17:27 +02:00
}
2018-04-29 23:58:18 +02:00
// Sending a request opens a new connection
2018-04-13 09:49:54 +02:00
await limiters . OpenConnectionsSemaphore . WaitAsync ( ) . ConfigureAwait ( false ) ;
2018-04-13 09:17:27 +02:00
2018-04-29 23:58:18 +02:00
try {
// It also increases number of requests
await limiters . RateLimitingSemaphore . WaitAsync ( ) . ConfigureAwait ( false ) ;
2018-04-13 09:17:27 +02:00
2018-04-29 23:58:18 +02:00
// We release rate-limiter semaphore regardless of our task completion, since we use that one only to guarantee rate-limiting of their creation
2018-06-16 07:47:07 +02:00
Utilities . InBackground (
async ( ) = > {
2019-01-14 21:50:23 +01:00
await Task . Delay ( ASF . GlobalConfig . WebLimiterDelay ) . ConfigureAwait ( false ) ;
2018-06-16 07:47:07 +02:00
limiters . RateLimitingSemaphore . Release ( ) ;
}
) ;
2018-04-13 09:49:54 +02:00
return await function ( ) . ConfigureAwait ( false ) ;
} finally {
2018-04-29 23:58:18 +02:00
// We release open connections semaphore only once we're indeed done sending a particular request
2018-04-13 09:49:54 +02:00
limiters . OpenConnectionsSemaphore . Release ( ) ;
}
2018-04-13 09:17:27 +02:00
}
2019-01-10 22:33:07 +01:00
public enum ESession : byte {
2018-03-06 19:15:49 +01:00
None ,
Lowercase ,
CamelCase
}
2017-01-11 15:22:00 +01:00
private enum ESteamApiKeyState : byte {
Error ,
2017-12-15 13:22:08 +01:00
Timeout ,
2017-01-11 15:22:00 +01:00
Registered ,
NotRegisteredYet ,
AccessDenied
}
2015-10-25 06:16:50 +01:00
}
2018-07-11 16:31:46 +02:00
}