Skip to content

Commit

Permalink
Added AllowCraftUnmarketableBoosters config setting
Browse files Browse the repository at this point in the history
  • Loading branch information
Citrinate committed Mar 9, 2024
1 parent 59c4354 commit 09f3cb8
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 14 deletions.
5 changes: 5 additions & 0 deletions BoosterManager/BoosterManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ public Task OnASFInit(IReadOnlyDictionary<string, JsonElement>? additionalConfig
BoosterHandler.AllowCraftUntradableBoosters = configProperty.Value.GetBoolean();
break;
}
case "AllowCraftUnmarketableBoosters" when (configProperty.Value.ValueKind == JsonValueKind.True || configProperty.Value.ValueKind == JsonValueKind.False): {
ASF.ArchiLogger.LogGenericInfo("Allow Craft Unmarketable Boosters : " + configProperty.Value);
BoosterHandler.AllowCraftUnmarketableBoosters = configProperty.Value.GetBoolean();
break;
}
case "BoosterDelayBetweenBots" when configProperty.Value.ValueKind == JsonValueKind.Number: {
ASF.ArchiLogger.LogGenericInfo("Booster Delay Between Bots : " + configProperty.Value);
BoosterHandler.UpdateBotDelays(configProperty.Value.GetInt32());
Expand Down
55 changes: 41 additions & 14 deletions BoosterManager/Boosters/BoosterQueue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,22 +103,35 @@ private async Task Run() {

internal void AddBooster(uint gameID, BoosterType type) {
void handler() {
if (BoosterInfos.TryGetValue(gameID, out Steam.BoosterInfo? boosterInfo)) {
try {
if (!BoosterInfos.TryGetValue(gameID, out Steam.BoosterInfo? boosterInfo)) {
Bot.ArchiLogger.LogGenericError(String.Format("Can't craft boosters for {0}", gameID));

return;
}

if (Boosters.TryGetValue(gameID, out Booster? existingBooster)) {
// Re-add a booster that was successfully crafted and is waiting to be cleared out of the queue
if (existingBooster.Type == BoosterType.OneTime && existingBooster.WasCrafted) {
RemoveBooster(gameID);
}
}

if (!BoosterHandler.AllowCraftUnmarketableBoosters && !MarketableApps.AppIDs.Contains(gameID)) {
Bot.ArchiLogger.LogGenericError(String.Format("Won't craft unmarketable boosters for {0}", gameID));

return;
}

Booster newBooster = new Booster(Bot, gameID, type, boosterInfo, this, GetLastCraft(gameID));
if (Boosters.TryAdd(gameID, newBooster)) {
Bot.ArchiLogger.LogGenericInfo(String.Format("Added {0} to booster queue.", gameID));
}
} else {
Bot.ArchiLogger.LogGenericError(String.Format("Can't craft boosters for {0}", gameID));
} finally {
OnBoosterInfosUpdated -= handler;
}
OnBoosterInfosUpdated -= handler;
}

OnBoosterInfosUpdated += handler;
}

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

if (!BoosterHandler.AllowCraftUnmarketableBoosters && !await MarketableApps.Update().ConfigureAwait(false)) {
return false;
}

(BoosterPageResponse? boosterPage, _) = await WebRequest.GetBoosterPage(Bot).ConfigureAwait(false);
if (boosterPage == null) {
Bot.ArchiLogger.LogNullError(boosterPage);
Expand Down Expand Up @@ -171,6 +188,7 @@ private async Task<Boolean> CraftBooster(Booster booster) {
} else {
nTp = TradabilityPreference.Default;
}

Steam.BoostersResponse? result = await booster.Craft(nTp).ConfigureAwait(false);
GooAmount = result?.GooAmount ?? GooAmount;
TradableGooAmount = result?.TradableGooAmount ?? TradableGooAmount;
Expand All @@ -184,8 +202,18 @@ private void VerifyCraftBoosterError(Booster booster) {
// 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.
// 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
void handler() {
Bot.ArchiLogger.LogGenericInfo(String.Format("An error was encountered when trying to craft a booster from {0}, trying to resolve it now", booster.GameID));
if (BoosterInfos.TryGetValue(booster.GameID, out Steam.BoosterInfo? newBoosterInfo)) {
try {
Bot.ArchiLogger.LogGenericInfo(String.Format("An error was encountered when trying to craft a booster from {0}, trying to resolve it now", booster.GameID));

if (!BoosterInfos.TryGetValue(booster.GameID, out Steam.BoosterInfo? newBoosterInfo)) {
// No longer have access to craft boosters for this game (game removed from account, or sometimes due to very rare Steam bugs)
BoosterHandler.PerpareStatusReport(String.Format("No longer able to craft boosters from {0} ({1})", booster.Info.Name, booster.GameID));
RemoveBooster(booster.GameID);
CheckIfFinished(booster.Type);

return;
}

if (newBoosterInfo.Unavailable && newBoosterInfo.AvailableAtTime != null
&& newBoosterInfo.AvailableAtTime != booster.Info.AvailableAtTime
&& (
Expand All @@ -196,17 +224,16 @@ void handler() {
Bot.ArchiLogger.LogGenericInfo(String.Format("Booster from {0} was recently created either by us or by user", booster.GameID));
booster.SetWasCrafted();
CheckIfFinished(booster.Type);
} else {
Bot.ArchiLogger.LogGenericInfo(String.Format("Booster from {0} was not created, retrying", booster.GameID));

return;
}
} else {
// No longer have access to craft boosters for this game (game removed from account, or sometimes due to very rare Steam bugs)
BoosterHandler.PerpareStatusReport(String.Format("No longer able to craft boosters from {0} ({1})", booster.Info.Name, booster.GameID));
RemoveBooster(booster.GameID);
CheckIfFinished(booster.Type);

Bot.ArchiLogger.LogGenericInfo(String.Format("Booster from {0} was not created, retrying", booster.GameID));
} finally {
OnBoosterInfosUpdated -= handler;
}
OnBoosterInfosUpdated -= handler;
}

OnBoosterInfosUpdated += handler;
}

Expand Down
47 changes: 47 additions & 0 deletions BoosterManager/Data/MarketableApps.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using ArchiSteamFarm.Core;
using ArchiSteamFarm.Web.Responses;

namespace BoosterManager {
internal static class MarketableApps {
internal static HashSet<uint> AppIDs = new();

private static Uri Source = new("https://raw.githubusercontent.com/Citrinate/Steam-MarketableApps/main/data/marketable_apps.min.json");
private static TimeSpan UpdateFrequency = TimeSpan.FromHours(1);
private static DateTime? LastUpdate;
private static SemaphoreSlim UpdateSemaphore = new SemaphoreSlim(1, 1);

internal static async Task<bool> Update() {
ArgumentNullException.ThrowIfNull(ASF.WebBrowser);

await UpdateSemaphore.WaitAsync().ConfigureAwait(false);
try {
if (LastUpdate != null && (LastUpdate + UpdateFrequency) > DateTime.Now) {
// Data is still fresh
return true;
}

// https://api.steampowered.com/ISteamApps/GetApplist/v2 can be used to get a list which includes all marketable apps and excludes all unmarketable apps
// 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)
// 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

ObjectResponse<HashSet<uint>>? response = await ASF.WebBrowser.UrlGetToJsonObject<HashSet<uint>>(Source).ConfigureAwait(false);
if (response == null || response.Content == null) {
ASF.ArchiLogger.LogGenericDebug("Failed to fetch marketable apps data");

return false;
}

AppIDs = response.Content;
LastUpdate = DateTime.Now;

return true;
} finally {
UpdateSemaphore.Release();
}
}
}
}
1 change: 1 addition & 0 deletions BoosterManager/Handlers/BoosterHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ internal sealed class BoosterHandler : IDisposable {
internal static ConcurrentDictionary<string, BoosterHandler> BoosterHandlers = new();
private static int DelayBetweenBots = 0; // Delay, in minutes, between when bots will craft boosters
internal static bool AllowCraftUntradableBoosters = true;
internal static bool AllowCraftUnmarketableBoosters = true;
private Timer? MarketRepeatTimer = null;

private BoosterHandler(Bot bot) {
Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,16 @@ Command | Alias |

---

### AllowCraftUnmarketableBoosters

`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.

```json
"AllowCraftUnmarketableBoosters": false,
```

---

### GamesToBooster

`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.
Expand Down

0 comments on commit 09f3cb8

Please sign in to comment.