Skip to content

Commit 09f3cb8

Browse files
committed
Added AllowCraftUnmarketableBoosters config setting
1 parent 59c4354 commit 09f3cb8

File tree

5 files changed

+104
-14
lines changed

5 files changed

+104
-14
lines changed

BoosterManager/BoosterManager.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ public Task OnASFInit(IReadOnlyDictionary<string, JsonElement>? additionalConfig
5353
BoosterHandler.AllowCraftUntradableBoosters = configProperty.Value.GetBoolean();
5454
break;
5555
}
56+
case "AllowCraftUnmarketableBoosters" when (configProperty.Value.ValueKind == JsonValueKind.True || configProperty.Value.ValueKind == JsonValueKind.False): {
57+
ASF.ArchiLogger.LogGenericInfo("Allow Craft Unmarketable Boosters : " + configProperty.Value);
58+
BoosterHandler.AllowCraftUnmarketableBoosters = configProperty.Value.GetBoolean();
59+
break;
60+
}
5661
case "BoosterDelayBetweenBots" when configProperty.Value.ValueKind == JsonValueKind.Number: {
5762
ASF.ArchiLogger.LogGenericInfo("Booster Delay Between Bots : " + configProperty.Value);
5863
BoosterHandler.UpdateBotDelays(configProperty.Value.GetInt32());

BoosterManager/Boosters/BoosterQueue.cs

Lines changed: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -103,22 +103,35 @@ private async Task Run() {
103103

104104
internal void AddBooster(uint gameID, BoosterType type) {
105105
void handler() {
106-
if (BoosterInfos.TryGetValue(gameID, out Steam.BoosterInfo? boosterInfo)) {
106+
try {
107+
if (!BoosterInfos.TryGetValue(gameID, out Steam.BoosterInfo? boosterInfo)) {
108+
Bot.ArchiLogger.LogGenericError(String.Format("Can't craft boosters for {0}", gameID));
109+
110+
return;
111+
}
112+
107113
if (Boosters.TryGetValue(gameID, out Booster? existingBooster)) {
108114
// Re-add a booster that was successfully crafted and is waiting to be cleared out of the queue
109115
if (existingBooster.Type == BoosterType.OneTime && existingBooster.WasCrafted) {
110116
RemoveBooster(gameID);
111117
}
112118
}
119+
120+
if (!BoosterHandler.AllowCraftUnmarketableBoosters && !MarketableApps.AppIDs.Contains(gameID)) {
121+
Bot.ArchiLogger.LogGenericError(String.Format("Won't craft unmarketable boosters for {0}", gameID));
122+
123+
return;
124+
}
125+
113126
Booster newBooster = new Booster(Bot, gameID, type, boosterInfo, this, GetLastCraft(gameID));
114127
if (Boosters.TryAdd(gameID, newBooster)) {
115128
Bot.ArchiLogger.LogGenericInfo(String.Format("Added {0} to booster queue.", gameID));
116129
}
117-
} else {
118-
Bot.ArchiLogger.LogGenericError(String.Format("Can't craft boosters for {0}", gameID));
130+
} finally {
131+
OnBoosterInfosUpdated -= handler;
119132
}
120-
OnBoosterInfosUpdated -= handler;
121133
}
134+
122135
OnBoosterInfosUpdated += handler;
123136
}
124137

@@ -144,6 +157,10 @@ private async Task<Boolean> UpdateBoosterInfos() {
144157
return true;
145158
}
146159

160+
if (!BoosterHandler.AllowCraftUnmarketableBoosters && !await MarketableApps.Update().ConfigureAwait(false)) {
161+
return false;
162+
}
163+
147164
(BoosterPageResponse? boosterPage, _) = await WebRequest.GetBoosterPage(Bot).ConfigureAwait(false);
148165
if (boosterPage == null) {
149166
Bot.ArchiLogger.LogNullError(boosterPage);
@@ -171,6 +188,7 @@ private async Task<Boolean> CraftBooster(Booster booster) {
171188
} else {
172189
nTp = TradabilityPreference.Default;
173190
}
191+
174192
Steam.BoostersResponse? result = await booster.Craft(nTp).ConfigureAwait(false);
175193
GooAmount = result?.GooAmount ?? GooAmount;
176194
TradableGooAmount = result?.TradableGooAmount ?? TradableGooAmount;
@@ -184,8 +202,18 @@ private void VerifyCraftBoosterError(Booster booster) {
184202
// Sometimes Steam will falsely report that an attempt to craft a booster failed, when it really didn't. It could also happen that the user crafted the booster on their own.
185203
// For any error we get, we'll need to refresh the booster page and see if the AvailableAtTime has changed to determine if we really failed to craft
186204
void handler() {
187-
Bot.ArchiLogger.LogGenericInfo(String.Format("An error was encountered when trying to craft a booster from {0}, trying to resolve it now", booster.GameID));
188-
if (BoosterInfos.TryGetValue(booster.GameID, out Steam.BoosterInfo? newBoosterInfo)) {
205+
try {
206+
Bot.ArchiLogger.LogGenericInfo(String.Format("An error was encountered when trying to craft a booster from {0}, trying to resolve it now", booster.GameID));
207+
208+
if (!BoosterInfos.TryGetValue(booster.GameID, out Steam.BoosterInfo? newBoosterInfo)) {
209+
// No longer have access to craft boosters for this game (game removed from account, or sometimes due to very rare Steam bugs)
210+
BoosterHandler.PerpareStatusReport(String.Format("No longer able to craft boosters from {0} ({1})", booster.Info.Name, booster.GameID));
211+
RemoveBooster(booster.GameID);
212+
CheckIfFinished(booster.Type);
213+
214+
return;
215+
}
216+
189217
if (newBoosterInfo.Unavailable && newBoosterInfo.AvailableAtTime != null
190218
&& newBoosterInfo.AvailableAtTime != booster.Info.AvailableAtTime
191219
&& (
@@ -196,17 +224,16 @@ void handler() {
196224
Bot.ArchiLogger.LogGenericInfo(String.Format("Booster from {0} was recently created either by us or by user", booster.GameID));
197225
booster.SetWasCrafted();
198226
CheckIfFinished(booster.Type);
199-
} else {
200-
Bot.ArchiLogger.LogGenericInfo(String.Format("Booster from {0} was not created, retrying", booster.GameID));
227+
228+
return;
201229
}
202-
} else {
203-
// No longer have access to craft boosters for this game (game removed from account, or sometimes due to very rare Steam bugs)
204-
BoosterHandler.PerpareStatusReport(String.Format("No longer able to craft boosters from {0} ({1})", booster.Info.Name, booster.GameID));
205-
RemoveBooster(booster.GameID);
206-
CheckIfFinished(booster.Type);
230+
231+
Bot.ArchiLogger.LogGenericInfo(String.Format("Booster from {0} was not created, retrying", booster.GameID));
232+
} finally {
233+
OnBoosterInfosUpdated -= handler;
207234
}
208-
OnBoosterInfosUpdated -= handler;
209235
}
236+
210237
OnBoosterInfosUpdated += handler;
211238
}
212239

BoosterManager/Data/MarketableApps.cs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Threading;
4+
using System.Threading.Tasks;
5+
using ArchiSteamFarm.Core;
6+
using ArchiSteamFarm.Web.Responses;
7+
8+
namespace BoosterManager {
9+
internal static class MarketableApps {
10+
internal static HashSet<uint> AppIDs = new();
11+
12+
private static Uri Source = new("https://raw.githubusercontent.com/Citrinate/Steam-MarketableApps/main/data/marketable_apps.min.json");
13+
private static TimeSpan UpdateFrequency = TimeSpan.FromHours(1);
14+
private static DateTime? LastUpdate;
15+
private static SemaphoreSlim UpdateSemaphore = new SemaphoreSlim(1, 1);
16+
17+
internal static async Task<bool> Update() {
18+
ArgumentNullException.ThrowIfNull(ASF.WebBrowser);
19+
20+
await UpdateSemaphore.WaitAsync().ConfigureAwait(false);
21+
try {
22+
if (LastUpdate != null && (LastUpdate + UpdateFrequency) > DateTime.Now) {
23+
// Data is still fresh
24+
return true;
25+
}
26+
27+
// https://api.steampowered.com/ISteamApps/GetApplist/v2 can be used to get a list which includes all marketable apps and excludes all unmarketable apps
28+
// It's however not reliable and also not perfect. At random times, tens of thousands of apps will be missing (some of which are marketable)
29+
// Can't account for these errors whithin this plugin (in a timely fashion), and so we use a cached version of ISteamApps/GetApplist which is known to be good
30+
31+
ObjectResponse<HashSet<uint>>? response = await ASF.WebBrowser.UrlGetToJsonObject<HashSet<uint>>(Source).ConfigureAwait(false);
32+
if (response == null || response.Content == null) {
33+
ASF.ArchiLogger.LogGenericDebug("Failed to fetch marketable apps data");
34+
35+
return false;
36+
}
37+
38+
AppIDs = response.Content;
39+
LastUpdate = DateTime.Now;
40+
41+
return true;
42+
} finally {
43+
UpdateSemaphore.Release();
44+
}
45+
}
46+
}
47+
}

BoosterManager/Handlers/BoosterHandler.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ internal sealed class BoosterHandler : IDisposable {
2020
internal static ConcurrentDictionary<string, BoosterHandler> BoosterHandlers = new();
2121
private static int DelayBetweenBots = 0; // Delay, in minutes, between when bots will craft boosters
2222
internal static bool AllowCraftUntradableBoosters = true;
23+
internal static bool AllowCraftUnmarketableBoosters = true;
2324
private Timer? MarketRepeatTimer = null;
2425

2526
private BoosterHandler(Bot bot) {

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,16 @@ Command | Alias |
197197

198198
---
199199

200+
### AllowCraftUnmarketableBoosters
201+
202+
`bool` type with default value of `true`. This configuration setting can be added to your `ASF.json` config file. If set to `false`, the plugin will not craft unmarketable boosters.
203+
204+
```json
205+
"AllowCraftUnmarketableBoosters": false,
206+
```
207+
208+
---
209+
200210
### GamesToBooster
201211

202212
`HashSet<uint>` type with default value of `[]`. This configuration setting can be added to your individual bot config files. It will automatically add all of the `AppIDs` to that bot's booster queue, and will automatically re-queue them after they've been crafted.

0 commit comments

Comments
 (0)