2019-02-16 17:34:17 +01:00
// _ _ _ ____ _ _____
2017-11-18 17:27:06 +01:00
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
2019-01-14 19:11:17 +01:00
// |
2022-01-06 20:22:38 +01:00
// Copyright 2015-2022 Ł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-12-04 01:07:37 +01:00
using System ;
using System.Collections.Generic ;
2018-12-14 16:07:46 +01:00
using System.Collections.Immutable ;
2020-11-14 22:37:00 +01:00
using System.Globalization ;
2018-02-21 17:44:06 +01:00
using System.Linq ;
2021-11-18 21:16:47 +01:00
using System.Net ;
Use IAsyncEnumerable for getting inventory (#1652)
* Use IAsyncEnumerable for getting inventory
* Don't suppress exceptions, catch them in ResponseUnpackBoosters
* Make sure we don't get duplicate assets during unpack
* Rewrite inventory filters to LINQ methods
* Add handling duplicate items, mark GetInventory as obsolete, catch exceptions from getting inventory errors
* Mark GetInventoryEnumerable as NotNull, don't check received inventory for null, use comparison with nullable values
* Use specific types of exceptions, log exceptions using LogGenericWarningException, handle IOException separately (without logging the exception), remove default null value
* Use old method signature for obsolete API
* Use error level for generic exceptions
* Fix wantedSets not being used
* Correct exception types, rename function
* Replace exception types
* Make SendTradeOfferAsync that accepts Func<Steam.Asset, bool> as a filter
* Fix missing targetSteamID in ResponseTransferByRealAppIDs
* Make parameter name readable
* Rename method
2020-02-22 20:03:22 +03:00
using System.Net.Http ;
2016-12-04 02:49:56 +01:00
using System.Threading ;
2016-12-04 01:07:37 +01:00
using System.Threading.Tasks ;
2022-12-15 18:46:37 +01:00
using ArchiSteamFarm.Core ;
2022-12-16 19:57:32 +01:00
using ArchiSteamFarm.Localization ;
2022-12-15 19:16:28 +01:00
using ArchiSteamFarm.OfficialPlugins.ItemsMatcher.Data ;
2021-05-08 01:37:22 +02:00
using ArchiSteamFarm.Steam ;
2021-05-08 12:49:46 +02:00
using ArchiSteamFarm.Steam.Cards ;
2021-05-08 01:37:22 +02:00
using ArchiSteamFarm.Steam.Data ;
using ArchiSteamFarm.Steam.Exchange ;
2022-12-17 17:23:20 +01:00
using ArchiSteamFarm.Steam.Integration ;
2021-05-08 01:37:22 +02:00
using ArchiSteamFarm.Steam.Security ;
using ArchiSteamFarm.Steam.Storage ;
using ArchiSteamFarm.Storage ;
Use custom WebBrowser for items matcher
Now this is dictated by at least several reasons:
- Firstly, we must have a WebBrowser per bot, and not per ASF instance, as we preserve ASF STM cookies that are on per-bot basis, which are required e.g. for Announce
- At the same time we shouldn't use Bot's one, because there are settings like WebProxy that shouldn't be used in regards to our own server
- We also require higher timeout than default one, especially for Announce, but also Inventories
- Best we can do is optimize that to not create a WebBrowser for bots that are neither configured for public listing, nor match actively. Since those settings need to be explicitly turned on, we shouldn't be duplicating WebBrowser per each bot instance, but rather only few selected bots configured to participate.
2022-12-23 18:21:43 +01:00
using ArchiSteamFarm.Web ;
2022-12-17 17:23:20 +01:00
using ArchiSteamFarm.Web.Responses ;
2016-12-04 01:07:37 +01:00
2022-12-15 18:46:37 +01:00
namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher ;
2021-11-10 21:23:24 +01:00
2022-06-19 18:24:52 +02:00
internal sealed class RemoteCommunication : IAsyncDisposable , IDisposable {
2022-12-16 19:57:32 +01:00
private const byte MaxAnnouncementTTL = 60 ; // Maximum amount of minutes we can wait before the next Announcement
private const byte MinAnnouncementTTL = 5 ; // Minimum amount of minutes we must wait before the next Announcement
2021-11-10 21:23:24 +01:00
private const byte MinHeartBeatTTL = 10 ; // Minimum amount of minutes we must wait before sending next HeartBeat
private const byte MinItemsCount = 100 ; // Minimum amount of items to be eligible for public listing
2022-12-16 19:57:32 +01:00
private const byte MinPersonaStateTTL = 5 ; // Minimum amount of minutes we must wait before requesting persona state update
2021-11-10 21:23:24 +01:00
private static readonly ImmutableHashSet < Asset . EType > AcceptedMatchableTypes = ImmutableHashSet . Create (
Asset . EType . Emoticon ,
Asset . EType . FoilTradingCard ,
Asset . EType . ProfileBackground ,
Asset . EType . TradingCard
) ;
private readonly Bot Bot ;
2022-12-15 18:46:37 +01:00
private readonly Timer ? HeartBeatTimer ;
2022-12-16 19:57:32 +01:00
private readonly SemaphoreSlim MatchActivelySemaphore = new ( 1 , 1 ) ;
2022-02-03 17:33:04 +01:00
private readonly Timer ? MatchActivelyTimer ;
2021-11-10 21:23:24 +01:00
private readonly SemaphoreSlim RequestsSemaphore = new ( 1 , 1 ) ;
Use custom WebBrowser for items matcher
Now this is dictated by at least several reasons:
- Firstly, we must have a WebBrowser per bot, and not per ASF instance, as we preserve ASF STM cookies that are on per-bot basis, which are required e.g. for Announce
- At the same time we shouldn't use Bot's one, because there are settings like WebProxy that shouldn't be used in regards to our own server
- We also require higher timeout than default one, especially for Announce, but also Inventories
- Best we can do is optimize that to not create a WebBrowser for bots that are neither configured for public listing, nor match actively. Since those settings need to be explicitly turned on, we shouldn't be duplicating WebBrowser per each bot instance, but rather only few selected bots configured to participate.
2022-12-23 18:21:43 +01:00
private readonly WebBrowser ? WebBrowser ;
2017-07-13 06:08:52 +02:00
2022-12-16 19:57:32 +01:00
private DateTime LastAnnouncement ;
2021-11-10 21:23:24 +01:00
private DateTime LastHeartBeat ;
private DateTime LastPersonaStateRequest ;
2022-12-16 19:57:32 +01:00
private bool ShouldSendAnnouncementEarlier ;
2021-11-10 21:23:24 +01:00
private bool ShouldSendHeartBeats ;
2022-12-17 17:23:20 +01:00
private bool SignedInWithSteam ;
2016-12-04 02:08:45 +01:00
2022-02-03 17:33:04 +01:00
internal RemoteCommunication ( Bot bot ) {
2022-12-15 18:46:37 +01:00
ArgumentNullException . ThrowIfNull ( bot ) ;
2016-12-04 02:08:45 +01:00
2022-12-15 18:46:37 +01:00
Bot = bot ;
Use custom WebBrowser for items matcher
Now this is dictated by at least several reasons:
- Firstly, we must have a WebBrowser per bot, and not per ASF instance, as we preserve ASF STM cookies that are on per-bot basis, which are required e.g. for Announce
- At the same time we shouldn't use Bot's one, because there are settings like WebProxy that shouldn't be used in regards to our own server
- We also require higher timeout than default one, especially for Announce, but also Inventories
- Best we can do is optimize that to not create a WebBrowser for bots that are neither configured for public listing, nor match actively. Since those settings need to be explicitly turned on, we shouldn't be duplicating WebBrowser per each bot instance, but rather only few selected bots configured to participate.
2022-12-23 18:21:43 +01:00
if ( ! Bot . BotConfig . RemoteCommunication . HasFlag ( BotConfig . ERemoteCommunication . PublicListing ) & & ! Bot . BotConfig . TradingPreferences . HasFlag ( BotConfig . ETradingPreferences . MatchActively ) ) {
return ;
}
2022-12-27 03:13:07 +01:00
WebBrowser = new WebBrowser ( bot . ArchiLogger , ASF . GlobalConfig ? . WebProxy , true ) ;
Use custom WebBrowser for items matcher
Now this is dictated by at least several reasons:
- Firstly, we must have a WebBrowser per bot, and not per ASF instance, as we preserve ASF STM cookies that are on per-bot basis, which are required e.g. for Announce
- At the same time we shouldn't use Bot's one, because there are settings like WebProxy that shouldn't be used in regards to our own server
- We also require higher timeout than default one, especially for Announce, but also Inventories
- Best we can do is optimize that to not create a WebBrowser for bots that are neither configured for public listing, nor match actively. Since those settings need to be explicitly turned on, we shouldn't be duplicating WebBrowser per each bot instance, but rather only few selected bots configured to participate.
2022-12-23 18:21:43 +01:00
2022-12-15 18:46:37 +01:00
if ( Bot . BotConfig . RemoteCommunication . HasFlag ( BotConfig . ERemoteCommunication . PublicListing ) ) {
HeartBeatTimer = new Timer (
HeartBeat ,
2022-02-03 17:33:04 +01:00
null ,
2022-12-16 19:57:32 +01:00
TimeSpan . FromMinutes ( 1 ) + TimeSpan . FromSeconds ( ASF . LoadBalancingDelay * Bot . Bots ? . Count ? ? 0 ) ,
2022-12-15 18:46:37 +01:00
TimeSpan . FromMinutes ( 1 )
2022-02-03 17:33:04 +01:00
) ;
}
2022-12-15 18:46:37 +01:00
if ( Bot . BotConfig . TradingPreferences . HasFlag ( BotConfig . ETradingPreferences . MatchActively ) ) {
if ( ( ASF . GlobalConfig ? . LicenseID ! = null ) & & ( ASF . GlobalConfig . LicenseID ! = Guid . Empty ) ) {
MatchActivelyTimer = new Timer (
MatchActively ,
null ,
TimeSpan . FromHours ( 1 ) + TimeSpan . FromSeconds ( ASF . LoadBalancingDelay * Bot . Bots ? . Count ? ? 0 ) ,
TimeSpan . FromHours ( 6 )
) ;
} else {
2022-12-16 19:57:32 +01:00
bot . ArchiLogger . LogGenericError ( string . Format ( CultureInfo . CurrentCulture , Strings . WarningNoLicense , nameof ( BotConfig . ETradingPreferences . MatchActively ) ) ) ;
2022-12-15 18:46:37 +01:00
}
}
2021-11-10 21:23:24 +01:00
}
2022-06-19 18:24:52 +02:00
public void Dispose ( ) {
// Those are objects that are always being created if constructor doesn't throw exception
MatchActivelySemaphore . Dispose ( ) ;
RequestsSemaphore . Dispose ( ) ;
// Those are objects that might be null and the check should be in-place
2022-12-15 18:46:37 +01:00
HeartBeatTimer ? . Dispose ( ) ;
Use custom WebBrowser for items matcher
Now this is dictated by at least several reasons:
- Firstly, we must have a WebBrowser per bot, and not per ASF instance, as we preserve ASF STM cookies that are on per-bot basis, which are required e.g. for Announce
- At the same time we shouldn't use Bot's one, because there are settings like WebProxy that shouldn't be used in regards to our own server
- We also require higher timeout than default one, especially for Announce, but also Inventories
- Best we can do is optimize that to not create a WebBrowser for bots that are neither configured for public listing, nor match actively. Since those settings need to be explicitly turned on, we shouldn't be duplicating WebBrowser per each bot instance, but rather only few selected bots configured to participate.
2022-12-23 18:21:43 +01:00
if ( MatchActivelyTimer ! = null ) {
// ReSharper disable once SuspiciousLockOverSynchronizationPrimitive - this is not a mistake, we need extra synchronization, and we can re-use the semaphore object for that
lock ( MatchActivelySemaphore ) {
MatchActivelyTimer . Dispose ( ) ;
}
}
WebBrowser ? . Dispose ( ) ;
2022-06-19 18:24:52 +02:00
}
2021-11-10 21:23:24 +01:00
public async ValueTask DisposeAsync ( ) {
2022-06-19 18:24:52 +02:00
// Those are objects that are always being created if constructor doesn't throw exception
2021-11-10 21:23:24 +01:00
MatchActivelySemaphore . Dispose ( ) ;
RequestsSemaphore . Dispose ( ) ;
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
2022-06-19 18:24:52 +02:00
// Those are objects that might be null and the check should be in-place
2022-12-15 18:46:37 +01:00
if ( HeartBeatTimer ! = null ) {
await HeartBeatTimer . DisposeAsync ( ) . ConfigureAwait ( false ) ;
}
2022-02-03 17:33:04 +01:00
if ( MatchActivelyTimer ! = null ) {
2022-12-17 18:27:41 +01:00
// ReSharper disable once SuspiciousLockOverSynchronizationPrimitive - this is not a mistake, we need extra synchronization, and we can re-use the semaphore object for that
lock ( MatchActivelySemaphore ) {
MatchActivelyTimer . Dispose ( ) ;
}
2022-02-03 17:33:04 +01:00
}
Use custom WebBrowser for items matcher
Now this is dictated by at least several reasons:
- Firstly, we must have a WebBrowser per bot, and not per ASF instance, as we preserve ASF STM cookies that are on per-bot basis, which are required e.g. for Announce
- At the same time we shouldn't use Bot's one, because there are settings like WebProxy that shouldn't be used in regards to our own server
- We also require higher timeout than default one, especially for Announce, but also Inventories
- Best we can do is optimize that to not create a WebBrowser for bots that are neither configured for public listing, nor match actively. Since those settings need to be explicitly turned on, we shouldn't be duplicating WebBrowser per each bot instance, but rather only few selected bots configured to participate.
2022-12-23 18:21:43 +01:00
WebBrowser ? . Dispose ( ) ;
2021-11-10 21:23:24 +01:00
}
2020-04-18 17:52:11 +02:00
2022-12-16 19:57:32 +01:00
internal void OnNewItemsNotification ( ) = > ShouldSendAnnouncementEarlier = true ;
2019-04-05 14:29:35 +02:00
2021-11-10 21:23:24 +01:00
internal async Task OnPersonaState ( string? nickname = null , string? avatarHash = null ) {
2022-12-29 22:25:35 +01:00
if ( ! Bot . BotConfig . RemoteCommunication . HasFlag ( BotConfig . ERemoteCommunication . PublicListing ) | | ! Bot . BotConfig . TradingPreferences . HasFlag ( BotConfig . ETradingPreferences . SteamTradeMatcher ) ) {
return ;
}
2022-12-30 17:22:21 +01:00
if ( WebBrowser = = null ) {
throw new InvalidOperationException ( nameof ( WebBrowser ) ) ;
}
2022-12-16 19:57:32 +01:00
if ( ( DateTime . UtcNow < LastAnnouncement . AddMinutes ( ShouldSendAnnouncementEarlier ? MinAnnouncementTTL : MaxAnnouncementTTL ) ) & & ShouldSendHeartBeats ) {
2021-11-10 21:23:24 +01:00
return ;
2019-05-19 15:38:06 +02:00
}
2017-11-26 19:08:48 +01:00
2021-11-10 21:23:24 +01:00
await RequestsSemaphore . WaitAsync ( ) . ConfigureAwait ( false ) ;
try {
2022-12-16 19:57:32 +01:00
if ( ( DateTime . UtcNow < LastAnnouncement . AddMinutes ( ShouldSendAnnouncementEarlier ? MinAnnouncementTTL : MaxAnnouncementTTL ) ) & & ShouldSendHeartBeats ) {
2017-03-19 21:56:36 +01:00
return ;
}
2021-11-10 21:23:24 +01:00
// Don't announce if we don't meet conditions
bool? eligible = await IsEligibleForListing ( ) . ConfigureAwait ( false ) ;
2016-12-04 02:49:56 +01:00
2021-11-10 21:23:24 +01:00
if ( ! eligible . HasValue ) {
// This is actually network failure, so we'll stop sending heartbeats but not record it as valid check
ShouldSendHeartBeats = false ;
2017-02-08 14:35:01 +01:00
2022-12-29 22:25:35 +01:00
Bot . ArchiLogger . LogGenericWarning ( string . Format ( CultureInfo . CurrentCulture , Strings . WarningFailedWithError , $"{nameof(IsEligibleForListing)}: {eligible?.ToString() ?? " null "}" ) ) ;
2021-11-10 21:23:24 +01:00
return ;
}
2018-12-15 00:27:15 +01:00
2021-11-10 21:23:24 +01:00
if ( ! eligible . Value ) {
2022-12-16 19:57:32 +01:00
// We're not eligible, record this as a valid check
LastAnnouncement = DateTime . UtcNow ;
ShouldSendAnnouncementEarlier = ShouldSendHeartBeats = false ;
2019-04-05 15:12:54 +02:00
2022-12-29 22:25:35 +01:00
Bot . ArchiLogger . LogGenericWarning ( string . Format ( CultureInfo . CurrentCulture , Strings . WarningFailedWithError , $"{nameof(IsEligibleForListing)}: {eligible.Value}" ) ) ;
2021-11-10 21:23:24 +01:00
return ;
}
2019-04-05 15:12:54 +02:00
2022-12-16 19:57:32 +01:00
HashSet < Asset . EType > acceptedMatchableTypes = Bot . BotConfig . MatchableTypes . Where ( AcceptedMatchableTypes . Contains ) . ToHashSet ( ) ;
2022-12-15 18:46:37 +01:00
2022-12-16 19:57:32 +01:00
if ( acceptedMatchableTypes . Count = = 0 ) {
throw new InvalidOperationException ( nameof ( acceptedMatchableTypes ) ) ;
2021-11-10 21:23:24 +01:00
}
2019-04-05 15:12:54 +02:00
2022-12-16 19:57:32 +01:00
string? tradeToken = await Bot . ArchiHandler . GetTradeToken ( ) . ConfigureAwait ( false ) ;
2019-04-05 15:12:54 +02:00
2022-12-16 19:57:32 +01:00
if ( string . IsNullOrEmpty ( tradeToken ) ) {
// This is actually a network failure, so we'll stop sending heartbeats but not record it as valid check
2021-11-10 21:23:24 +01:00
ShouldSendHeartBeats = false ;
2019-04-05 15:12:54 +02:00
2022-12-16 19:57:32 +01:00
Bot . ArchiLogger . LogGenericWarning ( string . Format ( CultureInfo . CurrentCulture , Strings . WarningFailedWithError , nameof ( tradeToken ) ) ) ;
2021-11-10 21:23:24 +01:00
return ;
}
2018-12-15 00:27:15 +01:00
2022-12-15 18:46:37 +01:00
List < Asset > inventory ;
2018-12-15 00:27:15 +01:00
2021-11-10 21:23:24 +01:00
try {
2022-12-15 18:46:37 +01:00
inventory = await Bot . ArchiWebHandler . GetInventoryAsync ( ) . ToListAsync ( ) . ConfigureAwait ( false ) ;
2021-11-10 21:23:24 +01:00
} catch ( HttpRequestException e ) {
2022-12-16 19:57:32 +01:00
// This is actually a network failure, so we'll stop sending heartbeats but not record it as valid check
2021-11-10 21:23:24 +01:00
ShouldSendHeartBeats = false ;
Use IAsyncEnumerable for getting inventory (#1652)
* Use IAsyncEnumerable for getting inventory
* Don't suppress exceptions, catch them in ResponseUnpackBoosters
* Make sure we don't get duplicate assets during unpack
* Rewrite inventory filters to LINQ methods
* Add handling duplicate items, mark GetInventory as obsolete, catch exceptions from getting inventory errors
* Mark GetInventoryEnumerable as NotNull, don't check received inventory for null, use comparison with nullable values
* Use specific types of exceptions, log exceptions using LogGenericWarningException, handle IOException separately (without logging the exception), remove default null value
* Use old method signature for obsolete API
* Use error level for generic exceptions
* Fix wantedSets not being used
* Correct exception types, rename function
* Replace exception types
* Make SendTradeOfferAsync that accepts Func<Steam.Asset, bool> as a filter
* Fix missing targetSteamID in ResponseTransferByRealAppIDs
* Make parameter name readable
* Rename method
2020-02-22 20:03:22 +03:00
2022-12-16 19:57:32 +01:00
Bot . ArchiLogger . LogGenericWarningException ( e ) ;
2021-11-10 21:23:24 +01:00
return ;
} catch ( Exception e ) {
2022-12-16 19:57:32 +01:00
// This is actually a network failure, so we'll stop sending heartbeats but not record it as valid check
2021-11-10 21:23:24 +01:00
ShouldSendHeartBeats = false ;
Use IAsyncEnumerable for getting inventory (#1652)
* Use IAsyncEnumerable for getting inventory
* Don't suppress exceptions, catch them in ResponseUnpackBoosters
* Make sure we don't get duplicate assets during unpack
* Rewrite inventory filters to LINQ methods
* Add handling duplicate items, mark GetInventory as obsolete, catch exceptions from getting inventory errors
* Mark GetInventoryEnumerable as NotNull, don't check received inventory for null, use comparison with nullable values
* Use specific types of exceptions, log exceptions using LogGenericWarningException, handle IOException separately (without logging the exception), remove default null value
* Use old method signature for obsolete API
* Use error level for generic exceptions
* Fix wantedSets not being used
* Correct exception types, rename function
* Replace exception types
* Make SendTradeOfferAsync that accepts Func<Steam.Asset, bool> as a filter
* Fix missing targetSteamID in ResponseTransferByRealAppIDs
* Make parameter name readable
* Rename method
2020-02-22 20:03:22 +03:00
2022-12-16 19:57:32 +01:00
Bot . ArchiLogger . LogGenericException ( e ) ;
2021-11-10 21:23:24 +01:00
return ;
}
2017-02-08 14:35:01 +01:00
2021-11-10 21:23:24 +01:00
// This is actual inventory
2022-12-15 18:46:37 +01:00
if ( inventory . Count ( item = > item . Tradable & & acceptedMatchableTypes . Contains ( item . Type ) ) < MinItemsCount ) {
2022-12-16 19:57:32 +01:00
// We're not eligible, record this as a valid check
LastAnnouncement = DateTime . UtcNow ;
ShouldSendAnnouncementEarlier = ShouldSendHeartBeats = false ;
2016-12-04 02:49:56 +01:00
2021-11-10 21:23:24 +01:00
return ;
}
2018-12-20 17:40:51 +01:00
2022-12-17 17:23:20 +01:00
if ( ! SignedInWithSteam ) {
2022-12-23 22:42:41 +01:00
HttpStatusCode ? signInWithSteam = await ArchiNet . SignInWithSteam ( Bot , WebBrowser ) . ConfigureAwait ( false ) ;
2022-12-17 17:23:20 +01:00
if ( signInWithSteam = = null ) {
// This is actually a network failure, so we'll stop sending heartbeats but not record it as valid check
ShouldSendHeartBeats = false ;
return ;
}
if ( ! signInWithSteam . Value . IsSuccessCode ( ) ) {
// SignIn procedure failed and it wasn't a network error, hold off with future tries at least for a full day
LastAnnouncement = DateTime . UtcNow . AddDays ( 1 ) ;
ShouldSendHeartBeats = false ;
return ;
}
SignedInWithSteam = true ;
}
2022-12-16 19:57:32 +01:00
Bot . ArchiLogger . LogGenericInfo ( string . Format ( CultureInfo . CurrentCulture , Localization . Strings . ListingAnnouncing , Bot . SteamID , nickname , inventory . Count ) ) ;
2022-12-15 18:46:37 +01:00
2021-11-18 21:16:47 +01:00
// ReSharper disable once RedundantSuppressNullableWarningExpression - required for .NET Framework
Use custom WebBrowser for items matcher
Now this is dictated by at least several reasons:
- Firstly, we must have a WebBrowser per bot, and not per ASF instance, as we preserve ASF STM cookies that are on per-bot basis, which are required e.g. for Announce
- At the same time we shouldn't use Bot's one, because there are settings like WebProxy that shouldn't be used in regards to our own server
- We also require higher timeout than default one, especially for Announce, but also Inventories
- Best we can do is optimize that to not create a WebBrowser for bots that are neither configured for public listing, nor match actively. Since those settings need to be explicitly turned on, we shouldn't be duplicating WebBrowser per each bot instance, but rather only few selected bots configured to participate.
2022-12-23 18:21:43 +01:00
BasicResponse ? response = await Backend . AnnounceForListing ( Bot , WebBrowser , inventory , acceptedMatchableTypes , tradeToken ! , nickname , avatarHash ) . ConfigureAwait ( false ) ;
2018-12-15 00:27:15 +01:00
2022-12-17 17:23:20 +01:00
if ( response = = null ) {
2022-12-16 19:57:32 +01:00
// This is actually a network failure, so we'll stop sending heartbeats but not record it as valid check
ShouldSendHeartBeats = false ;
2022-12-23 22:42:41 +01:00
Bot . ArchiLogger . LogGenericWarning ( string . Format ( CultureInfo . CurrentCulture , Strings . ErrorObjectIsNull , nameof ( response ) ) ) ;
2021-11-10 21:23:24 +01:00
return ;
}
2016-12-04 02:49:56 +01:00
2022-12-17 17:23:20 +01:00
if ( response . StatusCode . IsRedirectionCode ( ) ) {
ShouldSendHeartBeats = false ;
2022-12-15 18:46:37 +01:00
2022-12-23 22:42:41 +01:00
Bot . ArchiLogger . LogGenericWarning ( string . Format ( CultureInfo . CurrentCulture , Strings . WarningFailedWithError , response . StatusCode ) ) ;
2022-12-17 17:23:20 +01:00
if ( response . FinalUri . Host ! = ArchiWebHandler . SteamCommunityURL . Host ) {
ASF . ArchiLogger . LogGenericError ( string . Format ( CultureInfo . CurrentCulture , Strings . WarningUnknownValuePleaseReport , nameof ( response . FinalUri ) , response . FinalUri ) ) ;
return ;
}
// We've expected the result, not the redirection to the sign in, we need to authenticate again
SignedInWithSteam = false ;
return ;
}
if ( response . StatusCode . IsClientErrorCode ( ) ) {
2022-12-16 19:57:32 +01:00
// ArchiNet told us that we've sent a bad request, so the process should restart from the beginning at later time
2021-11-10 21:23:24 +01:00
ShouldSendHeartBeats = false ;
2019-04-05 16:24:02 +02:00
2022-12-23 22:42:41 +01:00
Bot . ArchiLogger . LogGenericWarning ( string . Format ( CultureInfo . CurrentCulture , Strings . WarningFailedWithError , response . StatusCode ) ) ;
2022-12-16 19:57:32 +01:00
2022-12-17 17:23:20 +01:00
switch ( response . StatusCode ) {
2022-12-15 18:46:37 +01:00
case HttpStatusCode . Forbidden :
// ArchiNet told us to stop submitting data for now
2022-12-16 19:57:32 +01:00
LastAnnouncement = DateTime . UtcNow . AddYears ( 1 ) ;
2022-12-15 18:46:37 +01:00
2022-12-17 17:23:20 +01:00
return ;
2022-12-15 18:46:37 +01:00
#if NETFRAMEWORK
2022-12-16 19:57:32 +01:00
case ( HttpStatusCode ) 429 :
2022-12-15 18:46:37 +01:00
#else
case HttpStatusCode . TooManyRequests :
#endif
// ArchiNet told us to try again later
2022-12-16 19:57:32 +01:00
LastAnnouncement = DateTime . UtcNow . AddDays ( 1 ) ;
2022-12-15 18:46:37 +01:00
2022-12-17 17:23:20 +01:00
return ;
default :
// There is something wrong with our payload or the server, we shouldn't retry for at least several hours
LastAnnouncement = DateTime . UtcNow . AddHours ( 6 ) ;
2022-12-15 18:46:37 +01:00
2022-12-17 17:23:20 +01:00
return ;
}
2021-11-10 21:23:24 +01:00
}
2019-04-05 16:24:02 +02:00
2022-12-17 17:23:20 +01:00
LastAnnouncement = LastHeartBeat = DateTime . UtcNow ;
ShouldSendAnnouncementEarlier = false ;
2021-11-10 21:23:24 +01:00
ShouldSendHeartBeats = true ;
2022-12-15 18:46:37 +01:00
2022-12-16 19:57:32 +01:00
Bot . ArchiLogger . LogGenericInfo ( Strings . Success ) ;
} finally {
RequestsSemaphore . Release ( ) ;
}
}
2022-12-17 18:27:41 +01:00
internal void TriggerMatchActivelyEarlier ( ) {
if ( MatchActivelyTimer = = null ) {
throw new InvalidOperationException ( nameof ( MatchActivelyTimer ) ) ;
}
// ReSharper disable once SuspiciousLockOverSynchronizationPrimitive - this is not a mistake, we need extra synchronization, and we can re-use the semaphore object for that
lock ( MatchActivelySemaphore ) {
MatchActivelyTimer . Change ( TimeSpan . Zero , TimeSpan . FromHours ( 6 ) ) ;
}
}
2022-12-16 19:57:32 +01:00
private async void HeartBeat ( object? state = null ) {
Use custom WebBrowser for items matcher
Now this is dictated by at least several reasons:
- Firstly, we must have a WebBrowser per bot, and not per ASF instance, as we preserve ASF STM cookies that are on per-bot basis, which are required e.g. for Announce
- At the same time we shouldn't use Bot's one, because there are settings like WebProxy that shouldn't be used in regards to our own server
- We also require higher timeout than default one, especially for Announce, but also Inventories
- Best we can do is optimize that to not create a WebBrowser for bots that are neither configured for public listing, nor match actively. Since those settings need to be explicitly turned on, we shouldn't be duplicating WebBrowser per each bot instance, but rather only few selected bots configured to participate.
2022-12-23 18:21:43 +01:00
if ( WebBrowser = = null ) {
throw new InvalidOperationException ( nameof ( WebBrowser ) ) ;
}
2022-12-16 19:57:32 +01:00
if ( ! Bot . IsConnectedAndLoggedOn | | ( Bot . HeartBeatFailures > 0 ) ) {
return ;
}
// Request persona update if needed
if ( ( DateTime . UtcNow > LastPersonaStateRequest . AddMinutes ( MinPersonaStateTTL ) ) & & ( DateTime . UtcNow > LastAnnouncement . AddMinutes ( ShouldSendAnnouncementEarlier ? MinAnnouncementTTL : MaxAnnouncementTTL ) ) ) {
LastPersonaStateRequest = DateTime . UtcNow ;
Bot . RequestPersonaStateUpdate ( ) ;
}
if ( ! ShouldSendHeartBeats | | ( DateTime . UtcNow < LastHeartBeat . AddMinutes ( MinHeartBeatTTL ) ) ) {
return ;
}
if ( ! await RequestsSemaphore . WaitAsync ( 0 ) . ConfigureAwait ( false ) ) {
return ;
}
try {
Use custom WebBrowser for items matcher
Now this is dictated by at least several reasons:
- Firstly, we must have a WebBrowser per bot, and not per ASF instance, as we preserve ASF STM cookies that are on per-bot basis, which are required e.g. for Announce
- At the same time we shouldn't use Bot's one, because there are settings like WebProxy that shouldn't be used in regards to our own server
- We also require higher timeout than default one, especially for Announce, but also Inventories
- Best we can do is optimize that to not create a WebBrowser for bots that are neither configured for public listing, nor match actively. Since those settings need to be explicitly turned on, we shouldn't be duplicating WebBrowser per each bot instance, but rather only few selected bots configured to participate.
2022-12-23 18:21:43 +01:00
BasicResponse ? response = await Backend . HeartBeatForListing ( Bot , WebBrowser ) . ConfigureAwait ( false ) ;
2022-12-16 19:57:32 +01:00
2022-12-23 16:34:42 +01:00
if ( response = = null ) {
// This is actually a network failure, we should keep sending heartbeats for now
2022-12-16 19:57:32 +01:00
return ;
}
2022-12-23 16:34:42 +01:00
if ( response . StatusCode . IsRedirectionCode ( ) ) {
ShouldSendHeartBeats = false ;
2022-12-23 22:42:41 +01:00
Bot . ArchiLogger . LogGenericWarning ( string . Format ( CultureInfo . CurrentCulture , Strings . WarningFailedWithError , response . StatusCode ) ) ;
2022-12-23 16:34:42 +01:00
if ( response . FinalUri . Host ! = ArchiWebHandler . SteamCommunityURL . Host ) {
ASF . ArchiLogger . LogGenericError ( string . Format ( CultureInfo . CurrentCulture , Strings . WarningUnknownValuePleaseReport , nameof ( response . FinalUri ) , response . FinalUri ) ) ;
return ;
}
// We've expected the result, not the redirection to the sign in, we need to authenticate again
SignedInWithSteam = false ;
return ;
}
if ( response . StatusCode . IsClientErrorCode ( ) ) {
2022-12-16 19:57:32 +01:00
ShouldSendHeartBeats = false ;
2022-12-23 22:42:41 +01:00
Bot . ArchiLogger . LogGenericWarning ( string . Format ( CultureInfo . CurrentCulture , Strings . WarningFailedWithError , response . StatusCode ) ) ;
2022-12-16 19:57:32 +01:00
return ;
}
LastHeartBeat = DateTime . UtcNow ;
2021-11-10 21:23:24 +01:00
} finally {
RequestsSemaphore . Release ( ) ;
}
}
2019-04-05 16:25:44 +02:00
2021-11-10 21:23:24 +01:00
private async Task < bool? > IsEligibleForListing ( ) {
2022-12-29 22:25:35 +01:00
// Bot must be eligible for matching
2021-11-10 21:23:24 +01:00
bool? isEligibleForMatching = await IsEligibleForMatching ( ) . ConfigureAwait ( false ) ;
2018-12-15 00:27:15 +01:00
2021-11-10 21:23:24 +01:00
if ( isEligibleForMatching ! = true ) {
return isEligibleForMatching ;
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
}
2022-12-29 22:25:35 +01:00
// Bot must have a public inventory
2021-11-10 21:23:24 +01:00
bool? hasPublicInventory = await Bot . HasPublicInventory ( ) . ConfigureAwait ( false ) ;
2019-06-19 18:50:26 +02:00
2021-11-10 21:23:24 +01:00
if ( hasPublicInventory ! = true ) {
2022-12-16 19:57:32 +01:00
Bot . ArchiLogger . LogGenericTrace ( string . Format ( CultureInfo . CurrentCulture , Strings . WarningFailedWithError , $"{nameof(Bot.HasPublicInventory)}: {hasPublicInventory?.ToString() ?? " null "}" ) ) ;
2019-06-19 18:50:26 +02:00
2021-11-10 21:23:24 +01:00
return hasPublicInventory ;
}
2019-06-19 18:50:26 +02:00
2021-11-10 21:23:24 +01:00
return true ;
}
2019-06-19 18:50:26 +02:00
2021-11-10 21:23:24 +01:00
private async Task < bool? > IsEligibleForMatching ( ) {
// Bot must have ASF 2FA
if ( ! Bot . HasMobileAuthenticator ) {
2022-12-16 19:57:32 +01:00
Bot . ArchiLogger . LogGenericTrace ( string . Format ( CultureInfo . CurrentCulture , Strings . WarningFailedWithError , $"{nameof(Bot.HasMobileAuthenticator)}: {Bot.HasMobileAuthenticator}" ) ) ;
2019-06-19 18:50:26 +02:00
2021-11-10 21:23:24 +01:00
return false ;
2019-06-19 18:50:26 +02:00
}
2021-11-10 21:23:24 +01:00
// Bot must have at least one accepted matchable type set
if ( ( Bot . BotConfig . MatchableTypes . Count = = 0 ) | | Bot . BotConfig . MatchableTypes . All ( static type = > ! AcceptedMatchableTypes . Contains ( type ) ) ) {
2022-12-16 19:57:32 +01:00
Bot . ArchiLogger . LogGenericTrace ( string . Format ( CultureInfo . CurrentCulture , Strings . WarningFailedWithError , $"{nameof(Bot.BotConfig.MatchableTypes)}: {string.Join(" , ", Bot.BotConfig.MatchableTypes)}" ) ) ;
2018-12-15 00:27:15 +01:00
2021-11-10 21:23:24 +01:00
return false ;
}
2018-03-24 17:14:01 +01:00
2021-11-10 21:23:24 +01:00
// Bot must have valid API key (e.g. not being restricted account)
bool? hasValidApiKey = await Bot . ArchiWebHandler . HasValidApiKey ( ) . ConfigureAwait ( false ) ;
2018-12-15 00:27:15 +01:00
2021-11-10 21:23:24 +01:00
if ( hasValidApiKey ! = true ) {
2022-12-16 19:57:32 +01:00
Bot . ArchiLogger . LogGenericTrace ( string . Format ( CultureInfo . CurrentCulture , Strings . WarningFailedWithError , $"{nameof(Bot.ArchiWebHandler.HasValidApiKey)}: {hasValidApiKey?.ToString() ?? " null "}" ) ) ;
2018-03-24 17:14:01 +01:00
2021-11-10 21:23:24 +01:00
return hasValidApiKey ;
}
2018-12-15 00:27:15 +01:00
2021-11-10 21:23:24 +01:00
return true ;
}
2019-04-05 15:12:54 +02:00
2021-11-10 21:23:24 +01:00
private async void MatchActively ( object? state = null ) {
Use custom WebBrowser for items matcher
Now this is dictated by at least several reasons:
- Firstly, we must have a WebBrowser per bot, and not per ASF instance, as we preserve ASF STM cookies that are on per-bot basis, which are required e.g. for Announce
- At the same time we shouldn't use Bot's one, because there are settings like WebProxy that shouldn't be used in regards to our own server
- We also require higher timeout than default one, especially for Announce, but also Inventories
- Best we can do is optimize that to not create a WebBrowser for bots that are neither configured for public listing, nor match actively. Since those settings need to be explicitly turned on, we shouldn't be duplicating WebBrowser per each bot instance, but rather only few selected bots configured to participate.
2022-12-23 18:21:43 +01:00
if ( WebBrowser = = null ) {
throw new InvalidOperationException ( nameof ( WebBrowser ) ) ;
}
2022-12-15 18:46:37 +01:00
if ( ASF . GlobalConfig = = null ) {
throw new InvalidOperationException ( nameof ( ASF . GlobalConfig ) ) ;
}
if ( ! ASF . GlobalConfig . LicenseID . HasValue | | ( ASF . GlobalConfig . LicenseID = = Guid . Empty ) ) {
throw new InvalidOperationException ( nameof ( ASF . GlobalConfig . LicenseID ) ) ;
}
2022-12-29 22:25:35 +01:00
if ( ! Bot . IsConnectedAndLoggedOn | | Bot . BotConfig . TradingPreferences . HasFlag ( BotConfig . ETradingPreferences . MatchEverything ) | | ! Bot . BotConfig . TradingPreferences . HasFlag ( BotConfig . ETradingPreferences . MatchActively ) ) {
2022-12-16 19:57:32 +01:00
Bot . ArchiLogger . LogGenericTrace ( Strings . ErrorAborted ) ;
2018-12-08 01:45:13 +01:00
2021-11-10 21:23:24 +01:00
return ;
2018-03-24 17:14:01 +01:00
}
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
2022-12-29 22:25:35 +01:00
bool? eligible = await IsEligibleForMatching ( ) . ConfigureAwait ( false ) ;
if ( eligible ! = true ) {
Bot . ArchiLogger . LogGenericWarning ( string . Format ( CultureInfo . CurrentCulture , Strings . WarningFailedWithError , $"{nameof(IsEligibleForMatching)}: {eligible?.ToString() ?? " null "}" ) ) ;
return ;
}
2021-11-10 21:23:24 +01:00
HashSet < Asset . EType > acceptedMatchableTypes = Bot . BotConfig . MatchableTypes . Where ( AcceptedMatchableTypes . Contains ) . ToHashSet ( ) ;
2018-12-02 06:24:36 +01:00
2021-11-10 21:23:24 +01:00
if ( acceptedMatchableTypes . Count = = 0 ) {
2022-12-15 18:46:37 +01:00
Bot . ArchiLogger . LogNullError ( acceptedMatchableTypes ) ;
2018-12-15 00:27:15 +01:00
2021-11-10 21:23:24 +01:00
return ;
}
2018-12-15 00:27:15 +01:00
2021-11-10 21:23:24 +01:00
if ( ! await MatchActivelySemaphore . WaitAsync ( 0 ) . ConfigureAwait ( false ) ) {
2022-12-16 19:57:32 +01:00
Bot . ArchiLogger . LogGenericTrace ( Strings . ErrorAborted ) ;
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
2021-11-10 21:23:24 +01:00
return ;
}
2018-12-15 00:27:15 +01:00
2021-11-10 21:23:24 +01:00
try {
2022-12-16 19:57:32 +01:00
Bot . ArchiLogger . LogGenericInfo ( Strings . Starting ) ;
2018-11-29 21:05:40 +01:00
2022-12-26 16:25:26 +01:00
Dictionary < ulong , Asset > ourInventory ;
2018-12-15 00:27:15 +01:00
2022-12-15 18:46:37 +01:00
try {
2022-12-26 16:25:26 +01:00
ourInventory = await Bot . ArchiWebHandler . GetInventoryAsync ( ) . Where ( item = > acceptedMatchableTypes . Contains ( item . Type ) & & ! Bot . BotDatabase . MatchActivelyBlacklistAppIDs . Contains ( item . RealAppID ) ) . ToDictionaryAsync ( static item = > item . AssetID ) . ConfigureAwait ( false ) ;
2022-12-15 18:46:37 +01:00
} catch ( HttpRequestException e ) {
Bot . ArchiLogger . LogGenericWarningException ( e ) ;
2022-12-16 19:57:32 +01:00
Bot . ArchiLogger . LogGenericWarning ( string . Format ( CultureInfo . CurrentCulture , Strings . WarningFailedWithError , nameof ( ourInventory ) ) ) ;
2022-12-15 18:46:37 +01:00
return ;
} catch ( Exception e ) {
Bot . ArchiLogger . LogGenericException ( e ) ;
2022-12-16 19:57:32 +01:00
Bot . ArchiLogger . LogGenericWarning ( string . Format ( CultureInfo . CurrentCulture , Strings . WarningFailedWithError , nameof ( ourInventory ) ) ) ;
2022-12-15 18:46:37 +01:00
return ;
}
if ( ourInventory . Count = = 0 ) {
2022-12-16 19:57:32 +01:00
Bot . ArchiLogger . LogGenericInfo ( string . Format ( CultureInfo . CurrentCulture , Strings . ErrorIsEmpty , nameof ( ourInventory ) ) ) ;
2022-12-15 18:46:37 +01:00
return ;
}
2022-12-23 15:08:36 +01:00
// Remove from our inventory items that can't be possibly matched due to no dupes to offer available
2022-12-26 16:25:26 +01:00
Dictionary < ( uint RealAppID , Asset . EType Type , Asset . ERarity Rarity ) , List < uint > > inventorySets = Trading . GetInventorySets ( ourInventory . Values ) ;
2022-12-23 15:08:36 +01:00
2022-12-26 16:25:26 +01:00
HashSet < ulong > assetIDsToRemove = ourInventory . Where ( item = > ! inventorySets . TryGetValue ( ( item . Value . RealAppID , item . Value . Type , item . Value . Rarity ) , out List < uint > ? amounts ) | | ( amounts . Count = = 0 ) | | amounts . All ( static amount = > amount < 2 ) ) . Select ( static item = > item . Key ) . ToHashSet ( ) ;
foreach ( ulong assetIDToRemove in assetIDsToRemove ) {
ourInventory . Remove ( assetIDToRemove ) ;
}
2022-12-23 15:08:36 +01:00
if ( ourInventory . Count = = 0 ) {
Bot . ArchiLogger . LogGenericInfo ( string . Format ( CultureInfo . CurrentCulture , Strings . ErrorIsEmpty , nameof ( ourInventory ) ) ) ;
return ;
}
2022-12-15 18:46:37 +01:00
// ReSharper disable once RedundantSuppressNullableWarningExpression - required for .NET Framework
2022-12-26 16:25:26 +01:00
( HttpStatusCode StatusCode , ImmutableHashSet < ListedUser > Users ) ? response = await Backend . GetListedUsersForMatching ( ASF . GlobalConfig . LicenseID . Value , Bot , WebBrowser , ourInventory . Values , acceptedMatchableTypes ) . ConfigureAwait ( false ) ;
2022-12-15 18:46:37 +01:00
if ( response = = null ) {
2022-12-16 19:57:32 +01:00
Bot . ArchiLogger . LogGenericWarning ( string . Format ( CultureInfo . CurrentCulture , Strings . WarningFailedWithError , nameof ( response ) ) ) ;
2022-12-15 18:46:37 +01:00
return ;
}
if ( ! response . Value . StatusCode . IsSuccessCode ( ) ) {
2022-12-16 19:57:32 +01:00
Bot . ArchiLogger . LogGenericWarning ( string . Format ( CultureInfo . CurrentCulture , Strings . WarningFailedWithError , response . Value . StatusCode ) ) ;
2022-12-15 18:46:37 +01:00
return ;
}
if ( response . Value . Users . IsEmpty ) {
2022-12-23 22:42:41 +01:00
Bot . ArchiLogger . LogGenericWarning ( string . Format ( CultureInfo . CurrentCulture , Strings . ErrorIsEmpty , nameof ( response . Value . Users ) ) ) ;
2022-12-15 18:46:37 +01:00
return ;
}
2018-12-03 05:21:12 +01:00
2021-05-07 23:56:45 +02:00
#pragma warning disable CA2000 // False positive, we're actually wrapping it in the using clause below exactly for that purpose
2022-12-15 18:46:37 +01:00
using ( await Bot . Actions . GetTradingLock ( ) . ConfigureAwait ( false ) ) {
2021-05-07 23:56:45 +02:00
#pragma warning restore CA2000 // False positive, we're actually wrapping it in the using clause below exactly for that purpose
2022-12-16 19:57:32 +01:00
Bot . ArchiLogger . LogGenericInfo ( Strings . Starting ) ;
2022-12-26 16:25:26 +01:00
await MatchActively ( response . Value . Users , ourInventory , acceptedMatchableTypes ) . 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
}
2022-12-16 19:57:32 +01:00
Bot . ArchiLogger . LogGenericInfo ( Strings . Done ) ;
2021-11-10 21:23:24 +01:00
} finally {
MatchActivelySemaphore . Release ( ) ;
}
}
2020-11-14 22:37:00 +01:00
2022-12-26 16:25:26 +01:00
private async Task MatchActively ( IReadOnlyCollection < ListedUser > listedUsers , Dictionary < ulong , Asset > ourInventory , IReadOnlyCollection < Asset . EType > acceptedMatchableTypes ) {
2022-12-15 18:46:37 +01:00
if ( ( listedUsers = = null ) | | ( listedUsers . Count = = 0 ) ) {
throw new ArgumentNullException ( nameof ( listedUsers ) ) ;
2021-11-10 21:23:24 +01:00
}
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
2022-12-15 18:46:37 +01:00
if ( ( ourInventory = = null ) | | ( ourInventory . Count = = 0 ) ) {
throw new ArgumentNullException ( nameof ( ourInventory ) ) ;
2021-11-10 21:23:24 +01:00
}
2018-12-15 00:27:15 +01:00
2022-12-15 18:46:37 +01:00
if ( ( acceptedMatchableTypes = = null ) | | ( acceptedMatchableTypes . Count = = 0 ) ) {
throw new ArgumentNullException ( nameof ( acceptedMatchableTypes ) ) ;
2021-11-10 21:23:24 +01:00
}
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
2022-12-26 16:25:26 +01:00
( Dictionary < ( uint RealAppID , Asset . EType Type , Asset . ERarity Rarity ) , Dictionary < ulong , uint > > ourFullState , Dictionary < ( uint RealAppID , Asset . EType Type , Asset . ERarity Rarity ) , Dictionary < ulong , uint > > ourTradableState ) = Trading . GetDividedInventoryState ( ourInventory . Values ) ;
2018-12-15 00:27:15 +01:00
2021-11-10 21:23:24 +01:00
if ( Trading . IsEmptyForMatching ( ourFullState , ourTradableState ) ) {
// User doesn't have any more dupes in the inventory
2022-12-16 19:57:32 +01:00
Bot . ArchiLogger . LogGenericTrace ( string . Format ( CultureInfo . CurrentCulture , Strings . ErrorIsEmpty , $"{nameof(ourFullState)} || {nameof(ourTradableState)}" ) ) ;
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
2022-12-15 18:46:37 +01:00
return ;
2021-11-10 21:23:24 +01:00
}
2018-12-15 00:27:15 +01:00
2021-11-10 21:23:24 +01:00
byte maxTradeHoldDuration = ASF . GlobalConfig ? . MaxTradeHoldDuration ? ? GlobalConfig . DefaultMaxTradeHoldDuration ;
2019-02-04 03:09:20 +01:00
2022-12-26 16:25:26 +01:00
uint matchedSets = 0 ;
foreach ( ListedUser listedUser in listedUsers . Where ( listedUser = > ( listedUser . SteamID ! = Bot . SteamID ) & & acceptedMatchableTypes . Any ( listedUser . MatchableTypes . Contains ) & & ! Bot . IsBlacklistedFromTrades ( listedUser . SteamID ) ) . OrderByDescending ( static listedUser = > listedUser . MatchEverything ) . ThenBy ( static listedUser = > listedUser . Assets . Count ) ) {
HashSet < ( uint RealAppID , Asset . EType Type , Asset . ERarity Rarity ) > wantedSets = ourTradableState . Keys . Where ( set = > listedUser . MatchableTypes . Contains ( set . Type ) ) . ToHashSet ( ) ;
2019-02-04 03:09:20 +01:00
2021-11-10 21:23:24 +01:00
if ( wantedSets . Count = = 0 ) {
continue ;
}
2019-08-26 00:49:34 +02:00
2021-11-10 21:23:24 +01:00
Bot . ArchiLogger . LogGenericTrace ( $"{listedUser.SteamID}..." ) ;
2020-08-06 20:03:15 +03:00
2022-06-11 18:07:06 +02:00
byte? tradeHoldDuration = await Bot . ArchiWebHandler . GetCombinedTradeHoldDurationAgainstUser ( listedUser . SteamID , listedUser . TradeToken ) . ConfigureAwait ( false ) ;
2020-08-06 20:03:15 +03:00
2022-06-11 18:07:06 +02:00
switch ( tradeHoldDuration ) {
2021-11-10 21:23:24 +01:00
case null :
2022-12-16 19:57:32 +01:00
Bot . ArchiLogger . LogGenericTrace ( string . Format ( CultureInfo . CurrentCulture , Strings . ErrorIsEmpty , nameof ( tradeHoldDuration ) ) ) ;
2020-08-06 20:03:15 +03:00
2021-11-10 21:23:24 +01:00
continue ;
2022-06-11 18:07:06 +02:00
case > 0 when ( tradeHoldDuration . Value > maxTradeHoldDuration ) | | ( tradeHoldDuration . Value > listedUser . MaxTradeHoldDuration ) :
Bot . ArchiLogger . LogGenericTrace ( $"{tradeHoldDuration.Value} > {maxTradeHoldDuration} || {listedUser.MaxTradeHoldDuration}" ) ;
2020-08-06 20:03:15 +03:00
2021-11-10 21:23:24 +01:00
continue ;
}
Use IAsyncEnumerable for getting inventory (#1652)
* Use IAsyncEnumerable for getting inventory
* Don't suppress exceptions, catch them in ResponseUnpackBoosters
* Make sure we don't get duplicate assets during unpack
* Rewrite inventory filters to LINQ methods
* Add handling duplicate items, mark GetInventory as obsolete, catch exceptions from getting inventory errors
* Mark GetInventoryEnumerable as NotNull, don't check received inventory for null, use comparison with nullable values
* Use specific types of exceptions, log exceptions using LogGenericWarningException, handle IOException separately (without logging the exception), remove default null value
* Use old method signature for obsolete API
* Use error level for generic exceptions
* Fix wantedSets not being used
* Correct exception types, rename function
* Replace exception types
* Make SendTradeOfferAsync that accepts Func<Steam.Asset, bool> as a filter
* Fix missing targetSteamID in ResponseTransferByRealAppIDs
* Make parameter name readable
* Rename method
2020-02-22 20:03:22 +03:00
2022-12-15 18:46:37 +01:00
HashSet < Asset > theirInventory = listedUser . Assets . Where ( item = > ( ! listedUser . MatchEverything | | item . Tradable ) & & wantedSets . Contains ( ( item . RealAppID , item . Type , item . Rarity ) ) & & ( ( tradeHoldDuration . Value = = 0 ) | | ! ( item . Type is Asset . EType . FoilTradingCard or Asset . EType . TradingCard & & CardsFarmer . SalesBlacklist . Contains ( item . RealAppID ) ) ) ) . Select ( static asset = > asset . ToAsset ( ) ) . ToHashSet ( ) ;
2018-12-09 18:08:14 +01:00
2022-12-17 13:09:01 +01:00
if ( theirInventory . Count = = 0 ) {
continue ;
}
2021-11-10 21:23:24 +01:00
HashSet < ( uint RealAppID , Asset . EType Type , Asset . ERarity Rarity ) > skippedSetsThisUser = new ( ) ;
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
2021-11-10 21:23:24 +01:00
Dictionary < ( uint RealAppID , Asset . EType Type , Asset . ERarity Rarity ) , Dictionary < ulong , uint > > theirTradableState = Trading . GetTradableInventoryState ( theirInventory ) ;
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
2021-11-10 21:23:24 +01:00
for ( byte i = 0 ; i < Trading . MaxTradesPerAccount ; i + + ) {
byte itemsInTrade = 0 ;
HashSet < ( uint RealAppID , Asset . EType Type , Asset . ERarity Rarity ) > skippedSetsThisTrade = new ( ) ;
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
2021-11-10 21:23:24 +01:00
Dictionary < ulong , uint > classIDsToGive = new ( ) ;
Dictionary < ulong , uint > classIDsToReceive = new ( ) ;
Dictionary < ulong , uint > fairClassIDsToGive = new ( ) ;
Dictionary < ulong , uint > fairClassIDsToReceive = new ( ) ;
2018-12-09 18:08:14 +01:00
2021-11-10 21:23:24 +01:00
foreach ( ( ( uint RealAppID , Asset . EType Type , Asset . ERarity Rarity ) set , Dictionary < ulong , uint > ourFullItems ) in ourFullState . Where ( set = > ! skippedSetsThisUser . Contains ( set . Key ) & & listedUser . MatchableTypes . Contains ( set . Key . Type ) & & set . Value . Values . Any ( static count = > count > 1 ) ) ) {
if ( ! ourTradableState . TryGetValue ( set , out Dictionary < ulong , uint > ? ourTradableItems ) | | ( ourTradableItems . Count = = 0 ) ) {
2022-12-26 16:25:26 +01:00
// We may have no more tradable items from this set
2021-11-10 21:23:24 +01:00
continue ;
}
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
2021-11-10 21:23:24 +01:00
if ( ! theirTradableState . TryGetValue ( set , out Dictionary < ulong , uint > ? theirTradableItems ) | | ( theirTradableItems . Count = = 0 ) ) {
2022-12-26 16:25:26 +01:00
// They may have no more tradable items from this set
continue ;
}
if ( Trading . IsEmptyForMatching ( ourFullItems , ourTradableItems ) ) {
// We may have no more matchable items from this set
2021-11-10 21:23:24 +01:00
continue ;
}
2018-12-09 21:26:22 +01:00
2022-12-26 16:25:26 +01:00
// Those 2 collections are on user-basis since we can't be sure that the trade passes through (and therefore we need to keep original state in case of a failure)
2021-11-10 21:23:24 +01:00
Dictionary < ulong , uint > ourFullSet = new ( ourFullItems ) ;
Dictionary < ulong , uint > ourTradableSet = new ( ourTradableItems ) ;
2018-12-15 00:27:15 +01:00
2021-11-10 21:23:24 +01:00
bool match ;
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
2021-11-10 21:23:24 +01:00
do {
match = false ;
2018-12-02 19:04:53 +01:00
2021-11-10 21:23:24 +01:00
foreach ( ( ulong ourItem , uint ourFullAmount ) in ourFullSet . Where ( static item = > item . Value > 1 ) . OrderByDescending ( static item = > item . Value ) ) {
if ( ! ourTradableSet . TryGetValue ( ourItem , out uint ourTradableAmount ) | | ( ourTradableAmount = = 0 ) ) {
continue ;
}
foreach ( ( ulong theirItem , uint theirTradableAmount ) in theirTradableItems . OrderBy ( item = > ourFullSet . TryGetValue ( item . Key , out uint ourAmountOfTheirItem ) ? ourAmountOfTheirItem : 0 ) ) {
if ( ourFullSet . TryGetValue ( theirItem , out uint ourAmountOfTheirItem ) & & ( ourFullAmount < = ourAmountOfTheirItem + 1 ) ) {
2018-12-09 18:08:14 +01:00
continue ;
}
2021-11-10 21:23:24 +01:00
if ( ! listedUser . MatchEverything ) {
// We have a potential match, let's check fairness for them
fairClassIDsToGive . TryGetValue ( ourItem , out uint fairGivenAmount ) ;
fairClassIDsToReceive . TryGetValue ( theirItem , out uint fairReceivedAmount ) ;
fairClassIDsToGive [ ourItem ] = + + fairGivenAmount ;
fairClassIDsToReceive [ theirItem ] = + + fairReceivedAmount ;
// Filter their inventory for the sets we're trading or have traded with this user
HashSet < Asset > fairFiltered = theirInventory . Where ( item = > ( ( item . RealAppID = = set . RealAppID ) & & ( item . Type = = set . Type ) & & ( item . Rarity = = set . Rarity ) ) | | skippedSetsThisTrade . Contains ( ( item . RealAppID , item . Type , item . Rarity ) ) ) . Select ( static item = > item . CreateShallowCopy ( ) ) . ToHashSet ( ) ;
// Copy list to HashSet<Steam.Asset>
2022-12-26 16:25:26 +01:00
HashSet < Asset > fairItemsToGive = Trading . GetTradableItemsFromInventory ( ourInventory . Values . Where ( item = > ( ( item . RealAppID = = set . RealAppID ) & & ( item . Type = = set . Type ) & & ( item . Rarity = = set . Rarity ) ) | | skippedSetsThisTrade . Contains ( ( item . RealAppID , item . Type , item . Rarity ) ) ) . Select ( static item = > item . CreateShallowCopy ( ) ) . ToHashSet ( ) , fairClassIDsToGive . ToDictionary ( static classID = > classID . Key , static classID = > classID . Value ) ) ;
2021-11-10 21:23:24 +01:00
HashSet < Asset > fairItemsToReceive = Trading . GetTradableItemsFromInventory ( fairFiltered . Select ( static item = > item . CreateShallowCopy ( ) ) . ToHashSet ( ) , fairClassIDsToReceive . ToDictionary ( static classID = > classID . Key , static classID = > classID . Value ) ) ;
2022-12-26 16:25:26 +01:00
// Actual check
2021-11-10 21:23:24 +01:00
if ( ! Trading . IsTradeNeutralOrBetter ( fairFiltered , fairItemsToReceive , fairItemsToGive ) ) {
2022-12-26 16:25:26 +01:00
// Revert the changes
2021-11-10 21:23:24 +01:00
if ( fairGivenAmount > 1 ) {
fairClassIDsToGive [ ourItem ] = fairGivenAmount - 1 ;
} else {
fairClassIDsToGive . Remove ( ourItem ) ;
2019-08-26 00:21:54 +02:00
}
2021-11-10 21:23:24 +01:00
if ( fairReceivedAmount > 1 ) {
fairClassIDsToReceive [ theirItem ] = fairReceivedAmount - 1 ;
} else {
fairClassIDsToReceive . Remove ( theirItem ) ;
}
2018-12-09 21:26:22 +01:00
2021-11-10 21:23:24 +01:00
continue ;
2018-12-09 21:26:22 +01:00
}
2021-11-10 21:23:24 +01:00
}
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
2021-11-10 21:23:24 +01:00
// Skip this set from the remaining of this round
skippedSetsThisTrade . Add ( set ) ;
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
2021-11-10 21:23:24 +01:00
// Update our state based on given items
classIDsToGive [ ourItem ] = classIDsToGive . TryGetValue ( ourItem , out uint ourGivenAmount ) ? ourGivenAmount + 1 : 1 ;
ourFullSet [ ourItem ] = ourFullAmount - 1 ; // We don't need to remove anything here because we can guarantee that ourItem.Value is at least 2
2018-12-09 18:08:14 +01:00
2021-11-10 21:23:24 +01:00
// Update our state based on received items
classIDsToReceive [ theirItem ] = classIDsToReceive . TryGetValue ( theirItem , out uint ourReceivedAmount ) ? ourReceivedAmount + 1 : 1 ;
ourFullSet [ theirItem ] = ourAmountOfTheirItem + 1 ;
2018-12-15 00:27:15 +01:00
2021-11-10 21:23:24 +01:00
if ( ourTradableAmount > 1 ) {
ourTradableSet [ ourItem ] = ourTradableAmount - 1 ;
} else {
ourTradableSet . Remove ( ourItem ) ;
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
}
2021-11-10 21:23:24 +01:00
// Update their state based on taken items
if ( theirTradableAmount > 1 ) {
theirTradableItems [ theirItem ] = theirTradableAmount - 1 ;
} else {
theirTradableItems . Remove ( theirItem ) ;
2018-12-02 19:04:53 +01:00
}
2021-11-10 21:23:24 +01:00
itemsInTrade + = 2 ;
match = true ;
break ;
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
}
2021-11-10 21:23:24 +01:00
if ( match ) {
break ;
}
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
}
2021-11-10 21:23:24 +01:00
} while ( match & & ( itemsInTrade < Trading . MaxItemsPerTrade - 1 ) ) ;
2018-12-15 00:27:15 +01:00
2021-11-10 21:23:24 +01:00
if ( itemsInTrade > = Trading . MaxItemsPerTrade - 1 ) {
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
break ;
}
2021-11-10 21:23:24 +01:00
}
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
2021-11-10 21:23:24 +01:00
if ( skippedSetsThisTrade . Count = = 0 ) {
2022-12-16 19:57:32 +01:00
Bot . ArchiLogger . LogGenericTrace ( string . Format ( CultureInfo . CurrentCulture , Strings . ErrorIsEmpty , nameof ( skippedSetsThisTrade ) ) ) ;
2018-12-09 18:08:14 +01:00
2021-11-10 21:23:24 +01:00
break ;
}
2018-12-15 00:27:15 +01:00
2021-11-10 21:23:24 +01:00
// Remove the items from inventories
2022-12-26 16:25:26 +01:00
HashSet < Asset > itemsToGive = Trading . GetTradableItemsFromInventory ( ourInventory . Values , classIDsToGive ) ;
2022-12-15 18:46:37 +01:00
HashSet < Asset > itemsToReceive = Trading . GetTradableItemsFromInventory ( theirInventory , classIDsToReceive , true ) ;
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
2021-11-10 21:23:24 +01:00
if ( ( itemsToGive . Count ! = itemsToReceive . Count ) | | ! Trading . IsFairExchange ( itemsToGive , itemsToReceive ) ) {
// Failsafe
2022-12-16 19:57:32 +01:00
Bot . ArchiLogger . LogGenericError ( string . Format ( CultureInfo . CurrentCulture , Strings . WarningFailedWithError , Strings . ErrorAborted ) ) ;
2018-12-15 00:27:15 +01:00
2022-12-15 18:46:37 +01:00
return ;
2021-11-10 21:23:24 +01:00
}
2018-12-05 23:13:55 +01:00
2022-12-16 19:57:32 +01:00
Bot . ArchiLogger . LogGenericInfo ( string . Format ( CultureInfo . CurrentCulture , Localization . Strings . MatchingFound , itemsToReceive . Count , listedUser . SteamID , listedUser . Nickname ) ) ;
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
2022-12-15 18:46:37 +01:00
Bot . ArchiLogger . LogGenericTrace ( $"{Bot.SteamID} <- {string.Join(" , ", itemsToReceive.Select(static item => $" { item . RealAppID } / { item . Type } / { item . Rarity } / { item . ClassID } # { item . Amount } "))} | {string.Join(" , ", itemsToGive.Select(static item => $" { item . RealAppID } / { item . Type } / { item . Rarity } / { item . ClassID } # { item . Amount } "))} -> {listedUser.SteamID}" ) ;
2019-01-23 17:58:37 +01:00
2021-11-10 21:23:24 +01:00
( bool success , HashSet < ulong > ? mobileTradeOfferIDs ) = await Bot . ArchiWebHandler . SendTradeOffer ( listedUser . SteamID , itemsToGive , itemsToReceive , listedUser . TradeToken , true ) . ConfigureAwait ( false ) ;
2018-12-15 00:27:15 +01:00
2021-11-10 21:23:24 +01:00
if ( ( mobileTradeOfferIDs ? . Count > 0 ) & & Bot . HasMobileAuthenticator ) {
( bool twoFactorSuccess , _ , _ ) = await Bot . Actions . HandleTwoFactorAuthenticationConfirmations ( true , Confirmation . EType . Trade , mobileTradeOfferIDs , true ) . 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
2021-11-10 21:23:24 +01:00
if ( ! twoFactorSuccess ) {
2022-12-16 19:57:32 +01:00
Bot . ArchiLogger . LogGenericWarning ( string . Format ( CultureInfo . CurrentCulture , Strings . WarningFailedWithError , nameof ( twoFactorSuccess ) ) ) ;
2018-12-15 00:27:15 +01:00
2022-12-15 18:46:37 +01:00
return ;
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
}
}
2021-11-10 21:23:24 +01:00
if ( ! success ) {
2022-12-16 19:57:32 +01:00
Bot . ArchiLogger . LogGenericWarning ( string . Format ( CultureInfo . CurrentCulture , Localization . Strings . TradeOfferFailed , listedUser . SteamID , listedUser . Nickname ) ) ;
2018-12-05 19:13:46 +01:00
2021-11-10 21:23:24 +01:00
break ;
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
}
2022-12-26 16:25:26 +01:00
Bot . ArchiLogger . LogGenericInfo ( Strings . Success ) ;
// Assume the trade offer has went through and was accepted, this will allow us to keep matching the same set with different users as if we've got what we wanted
foreach ( Asset itemToGive in itemsToGive ) {
if ( ! ourInventory . TryGetValue ( itemToGive . AssetID , out Asset ? item ) | | ( itemToGive . Amount > item . Amount ) ) {
throw new InvalidOperationException ( nameof ( item ) ) ;
}
if ( itemToGive . Amount = = item . Amount ) {
ourInventory . Remove ( itemToGive . AssetID ) ;
} else {
item . Amount - = itemToGive . Amount ;
}
if ( ! ourFullState . TryGetValue ( ( itemToGive . RealAppID , itemToGive . Type , itemToGive . Rarity ) , out Dictionary < ulong , uint > ? fullAmounts ) | | ! fullAmounts . TryGetValue ( itemToGive . ClassID , out uint fullAmount ) | | ( itemToGive . Amount > fullAmount ) ) {
// We're giving items we don't even have?
throw new InvalidOperationException ( nameof ( fullAmounts ) ) ;
}
if ( itemToGive . Amount = = fullAmount ) {
fullAmounts . Remove ( itemToGive . ClassID ) ;
} else {
fullAmounts [ itemToGive . ClassID ] = fullAmount - itemToGive . Amount ;
}
if ( ! ourTradableState . TryGetValue ( ( itemToGive . RealAppID , itemToGive . Type , itemToGive . Rarity ) , out Dictionary < ulong , uint > ? tradableAmounts ) | | ! tradableAmounts . TryGetValue ( itemToGive . ClassID , out uint tradableAmount ) | | ( itemToGive . Amount > tradableAmount ) ) {
// We're giving items we don't even have?
throw new InvalidOperationException ( nameof ( tradableAmounts ) ) ;
}
if ( itemToGive . Amount = = tradableAmount ) {
tradableAmounts . Remove ( itemToGive . ClassID ) ;
} else {
tradableAmounts [ itemToGive . ClassID ] = tradableAmount - itemToGive . Amount ;
}
}
// However, since this is only an assumption, we must mark newly acquired items as untradable so we're sure that they're not considered for trading, only for matching
foreach ( Asset itemToReceive in itemsToReceive ) {
if ( ourInventory . TryGetValue ( itemToReceive . AssetID , out Asset ? item ) ) {
item . Tradable = false ;
item . Amount + = itemToReceive . Amount ;
} else {
itemToReceive . Tradable = false ;
ourInventory [ itemToReceive . AssetID ] = itemToReceive ;
}
if ( ! ourFullState . TryGetValue ( ( itemToReceive . RealAppID , itemToReceive . Type , itemToReceive . Rarity ) , out Dictionary < ulong , uint > ? fullAmounts ) ) {
// We're receiving items from a set we don't even have?
throw new InvalidOperationException ( nameof ( fullAmounts ) ) ;
}
if ( ! fullAmounts . TryGetValue ( itemToReceive . ClassID , out uint fullAmount ) ) {
fullAmount = 0 ;
}
fullAmounts [ itemToReceive . ClassID ] = itemToReceive . Amount + fullAmount ;
}
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
2021-11-10 21:23:24 +01:00
skippedSetsThisUser . UnionWith ( skippedSetsThisTrade ) ;
}
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
2021-11-10 21:23:24 +01:00
if ( skippedSetsThisUser . Count = = 0 ) {
continue ;
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
}
2022-12-26 16:25:26 +01:00
matchedSets + = ( uint ) skippedSetsThisUser . Count ;
2021-11-10 21:23:24 +01:00
if ( Trading . IsEmptyForMatching ( ourFullState , ourTradableState ) ) {
// User doesn't have any more dupes in the inventory
break ;
}
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
}
2022-12-26 16:25:26 +01:00
Bot . ArchiLogger . LogGenericInfo ( string . Format ( CultureInfo . CurrentCulture , Localization . Strings . ActivelyMatchingItemsRound , matchedSets ) ) ;
2021-11-10 21:23:24 +01:00
}
2018-09-08 00:46:40 +02:00
}