Skip to content

Commit

Permalink
Refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
Citrinate committed Apr 7, 2024
1 parent bfb923f commit 1d8a793
Show file tree
Hide file tree
Showing 6 changed files with 180 additions and 144 deletions.
10 changes: 4 additions & 6 deletions BoosterManager/Boosters/BoosterQueue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
namespace BoosterManager {
internal sealed class BoosterQueue : IDisposable {
private readonly Bot Bot;
private readonly BoosterHandler BoosterHandler;
private readonly Timer Timer;
private readonly ConcurrentDictionary<uint, Booster> Boosters = new();
private Dictionary<uint, Steam.BoosterInfo> BoosterInfos = new();
Expand All @@ -23,9 +22,8 @@ internal sealed class BoosterQueue : IDisposable {
internal event Action? OnBoosterInfosUpdated;
private float BoosterInfosUpdateBackOffMultiplier = 1.0F;

internal BoosterQueue(Bot bot, BoosterHandler boosterHandler) {
internal BoosterQueue(Bot bot) {
Bot = bot;
BoosterHandler = boosterHandler;
Timer = new Timer(
async e => await Run().ConfigureAwait(false),
null,
Expand Down Expand Up @@ -73,7 +71,7 @@ private async Task Run() {

if (DateTime.Now >= booster.GetAvailableAtTime(BoosterDelay)) {
if (booster.Info.Price > GetAvailableGems()) {
BoosterHandler.PerpareStatusReport(String.Format(Strings.NotEnoughGems, String.Format("{0:N0}", GetGemsNeeded(BoosterType.Any, wasCrafted: false) - GetAvailableGems())), suppressDuplicateMessages: true);
BoosterHandler.GeneralReporter.Report(Bot, String.Format(Strings.NotEnoughGems, String.Format("{0:N0}", GetGemsNeeded(BoosterType.Any, wasCrafted: false) - GetAvailableGems())), suppressDuplicateMessages: true);
OnBoosterInfosUpdated += ForceUpdateBoosterInfos;
UpdateTimer(DateTime.Now.AddMinutes(Math.Min(15, (GetNumBoosters(BoosterType.OneTime) > 0 ? 1 : 15) * BoosterInfosUpdateBackOffMultiplier)));
BoosterInfosUpdateBackOffMultiplier += 0.5F;
Expand Down Expand Up @@ -216,7 +214,7 @@ void handler() {

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(Strings.BoosterUnexpectedlyUncraftable, booster.Info.Name, booster.GameID));
BoosterHandler.GeneralReporter.Report(Bot, String.Format(Strings.BoosterUnexpectedlyUncraftable, booster.Info.Name, booster.GameID));
RemoveBooster(booster.GameID);
CheckIfFinished(booster.Type);

Expand Down Expand Up @@ -267,7 +265,7 @@ internal bool CheckIfFinished(BoosterType type) {
}

if (type == BoosterType.OneTime) {
BoosterHandler.PerpareStatusReport(String.Format(Strings.BoosterCreationFinished, GetNumBoosters(BoosterType.OneTime)));
BoosterHandler.GeneralReporter.Report(Bot, String.Format(Strings.BoosterCreationFinished, GetNumBoosters(BoosterType.OneTime)));
}
ClearCraftedBoosters(type);

Expand Down
40 changes: 20 additions & 20 deletions BoosterManager/Commands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ internal static class Commands {
return ResponseBoosterStatus(bot, access);

case "BSA^":
return ResponseBoosterStatus(access, steamID, "ASF", true);
return ResponseBoosterStatus(access, steamID, "ASF", shortStatus: true);
case "BSTATUS^" or "BOOSTERSTATUS^":
return ResponseBoosterStatus(bot, access, true);
return ResponseBoosterStatus(bot, access, shortStatus: true);

case "BSTOPALL" or "BOOSTERSTOPALL":
return ResponseBoosterStopTime(bot, access, "0");
Expand Down Expand Up @@ -108,7 +108,7 @@ internal static class Commands {
return ResponseLogStop(bot, access);

case "LOGINVENTORYHISTORY" or "SENDINVENTORYHISTORY" or "LOGIH" or "SENDIH":
return await ResponseLogInventoryHistory(bot, access, steamID).ConfigureAwait(false);
return await ResponseLogInventoryHistory(bot, access, new StatusReporter(bot, steamID)).ConfigureAwait(false);

case "LOGMARKETHISTORY" or "SENDMARKETHISTORY" or "LOGMH" or "SENDMH":
return await ResponseLogMarketHistory(bot, access).ConfigureAwait(false);
Expand Down Expand Up @@ -209,9 +209,9 @@ internal static class Commands {
default:
switch (args[0].ToUpperInvariant()) {
case "BOOSTER" when args.Length > 2:
return ResponseBooster(access, steamID, args[1], Utilities.GetArgsAsText(args, 2, ","), bot);
return ResponseBooster(access, steamID, new StatusReporter(bot, steamID), args[1], Utilities.GetArgsAsText(args, 2, ","));
case "BOOSTER":
return ResponseBooster(bot, access, steamID, args[1]);
return ResponseBooster(bot, access, steamID, new StatusReporter(bot, steamID), args[1]);

case "BOOSTERS" or "MBOOSTERS":
return await ResponseCountItems(access, steamID, Utilities.GetArgsAsText(args, 1, ","), ItemIdentifier.BoosterIdentifier, marketable: true).ConfigureAwait(false);
Expand All @@ -230,7 +230,7 @@ internal static class Commands {
return ResponseBoosterStatus(access, steamID, args[1]);

case "BSTATUS^" or "BOOSTERSTATUS^":
return ResponseBoosterStatus(access, steamID, args[1], true);
return ResponseBoosterStatus(access, steamID, args[1], shortStatus: true);

case "BSTOP" or "BOOSTERSTOP" when args.Length > 2:
return ResponseBoosterStop(access, steamID, args[1], Utilities.GetArgsAsText(args, 2, ","));
Expand Down Expand Up @@ -291,13 +291,13 @@ internal static class Commands {
return ResponseLogStop(access, steamID, Utilities.GetArgsAsText(args, 1, ","));

case "LOGINVENTORYHISTORY" or "SENDINVENTORYHISTORY" or "LOGIH" or "SENDIH" when args.Length > 5:
return await ResponseLogInventoryHistory(access, steamID, bot, args[1], args[2], args[3], args[4], args[5]).ConfigureAwait(false);
return await ResponseLogInventoryHistory(access, steamID, new StatusReporter(bot, steamID), args[1], args[2], args[3], args[4], args[5]).ConfigureAwait(false);
case "LOGINVENTORYHISTORY" or "SENDINVENTORYHISTORY" or "LOGIH" or "SENDIH" when args.Length > 3:
return await ResponseLogInventoryHistory(access, steamID, bot, args[1], args[2], args[3]).ConfigureAwait(false);
return await ResponseLogInventoryHistory(access, steamID, new StatusReporter(bot, steamID), args[1], args[2], args[3]).ConfigureAwait(false);
case "LOGINVENTORYHISTORY" or "SENDINVENTORYHISTORY" or "LOGIH" or "SENDIH" when args.Length > 2:
return await ResponseLogInventoryHistory(access, steamID, bot, args[1], args[2]).ConfigureAwait(false);
return await ResponseLogInventoryHistory(access, steamID, new StatusReporter(bot, steamID), args[1], args[2]).ConfigureAwait(false);
case "LOGINVENTORYHISTORY" or "SENDINVENTORYHISTORY" or "LOGIH" or "SENDIH":
return await ResponseLogInventoryHistory(access, steamID, bot, args[1]).ConfigureAwait(false);
return await ResponseLogInventoryHistory(access, steamID, new StatusReporter(bot, steamID), args[1]).ConfigureAwait(false);

case "LOGMARKETHISTORY" or "SENDMARKETHISTORY" or "LOGMH" or "SENDMH" when args.Length > 3:
return await ResponseLogMarketHistory(access, steamID, args[1], args[2], args[3]).ConfigureAwait(false);
Expand Down Expand Up @@ -511,13 +511,13 @@ internal static class Commands {
}

if (minutes == 0) {
if (BoosterHandler.BoosterHandlers[bot.BotName].StopMarketTimer()) {
if (MarketHandler.StopMarketRepeatTimer(bot)) {
return FormatBotResponse(bot, Strings.RepetitionCancelled);
} else {
return FormatBotResponse(bot, Strings.RepetitionNotActive);
}
} else {
BoosterHandler.BoosterHandlers[bot.BotName].StartMarketTimer(minutes);
MarketHandler.StartMarketRepeatTimer(bot, minutes);
repeatMessage = String.Format(Strings.RepetitionNotice, minutes, String.Format("!m2faok {0} 0", bot.BotName));
}
}
Expand Down Expand Up @@ -550,7 +550,7 @@ internal static class Commands {
return responses.Count > 0 ? string.Join(Environment.NewLine, responses) : null;
}

private static string? ResponseBooster(Bot bot, EAccess access, ulong steamID, string targetGameIDs, Bot? respondingBot = null) {
private static string? ResponseBooster(Bot bot, EAccess access, ulong steamID, StatusReporter craftingReporter, string targetGameIDs) {
if (String.IsNullOrEmpty(targetGameIDs)) {
throw new ArgumentNullException(nameof(targetGameIDs));
}
Expand Down Expand Up @@ -579,10 +579,10 @@ internal static class Commands {
gamesToBooster.Add(gameID);
}

return BoosterHandler.BoosterHandlers[bot.BotName].ScheduleBoosters(gamesToBooster, respondingBot ?? bot, steamID);
return BoosterHandler.BoosterHandlers[bot.BotName].ScheduleBoosters(gamesToBooster, craftingReporter);
}

private static string? ResponseBooster(EAccess access, ulong steamID, string botNames, string targetGameIDs, Bot respondingBot) {
private static string? ResponseBooster(EAccess access, ulong steamID, StatusReporter craftingReporter, string botNames, string targetGameIDs) {
if (String.IsNullOrEmpty(botNames)) {
throw new ArgumentNullException(nameof(botNames));
}
Expand All @@ -593,7 +593,7 @@ internal static class Commands {
return access >= EAccess.Owner ? FormatStaticResponse(String.Format(ArchiSteamFarm.Localization.Strings.BotNotFound, botNames)) : null;
}

IEnumerable<string?> results = bots.Select(bot => ResponseBooster(bot, ArchiSteamFarm.Steam.Interaction.Commands.GetProxyAccess(bot, access, steamID), steamID, targetGameIDs, respondingBot));
IEnumerable<string?> results = bots.Select(bot => ResponseBooster(bot, ArchiSteamFarm.Steam.Interaction.Commands.GetProxyAccess(bot, access, steamID), steamID, craftingReporter, targetGameIDs));

List<string?> responses = new(results.Where(result => !String.IsNullOrEmpty(result)));

Expand Down Expand Up @@ -1089,7 +1089,7 @@ internal static class Commands {
return responses.Count > 0 ? String.Join(Environment.NewLine, responses) : null;
}

private static async Task<string?> ResponseLogInventoryHistory(Bot bot, EAccess access, ulong steamID, string? numPagesString = null, string? startTimeString = null, string? timeFracString = null, string? sString = null, Bot? respondingBot = null) {
private static async Task<string?> ResponseLogInventoryHistory(Bot bot, EAccess access, StatusReporter rateLimitReporter, string? numPagesString = null, string? startTimeString = null, string? timeFracString = null, string? sString = null) {
if (access < EAccess.Master) {
return null;
}
Expand Down Expand Up @@ -1134,10 +1134,10 @@ internal static class Commands {
}
}

return await DataHandler.SendInventoryHistoryOnly(bot, respondingBot ?? bot, steamID, numPages, startTime, timeFrac, s).ConfigureAwait(false);
return await DataHandler.SendInventoryHistoryOnly(bot, rateLimitReporter, numPages, startTime, timeFrac, s).ConfigureAwait(false);
}

private static async Task<string?> ResponseLogInventoryHistory(EAccess access, ulong steamID, Bot respondingBot, string botNames, string? numPagesString = null, string? startTimeString = null, string? timeFracString = null, string? sString = null) {
private static async Task<string?> ResponseLogInventoryHistory(EAccess access, ulong steamID, StatusReporter rateLimitReporter, string botNames, string? numPagesString = null, string? startTimeString = null, string? timeFracString = null, string? sString = null) {
if (String.IsNullOrEmpty(botNames)) {
throw new ArgumentNullException(nameof(botNames));
}
Expand All @@ -1148,7 +1148,7 @@ internal static class Commands {
return access >= EAccess.Owner ? FormatStaticResponse(String.Format(ArchiSteamFarm.Localization.Strings.BotNotFound, botNames)) : null;
}

IList<string?> results = await Utilities.InParallel(bots.Select(bot => ResponseLogInventoryHistory(bot, ArchiSteamFarm.Steam.Interaction.Commands.GetProxyAccess(bot, access, steamID), steamID, numPagesString, startTimeString, timeFracString, sString, respondingBot))).ConfigureAwait(false);
IList<string?> results = await Utilities.InParallel(bots.Select(bot => ResponseLogInventoryHistory(bot, ArchiSteamFarm.Steam.Interaction.Commands.GetProxyAccess(bot, access, steamID), rateLimitReporter, numPagesString, startTimeString, timeFracString, sString))).ConfigureAwait(false);

List<string?> responses = new(results.Where(result => !String.IsNullOrEmpty(result)));

Expand Down
130 changes: 25 additions & 105 deletions BoosterManager/Handlers/BoosterHandler.cs
Original file line number Diff line number Diff line change
@@ -1,39 +1,28 @@
using ArchiSteamFarm.Core;
using ArchiSteamFarm.Steam;
using BoosterManager.Localization;
using SteamKit2;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace BoosterManager {
internal sealed class BoosterHandler : IDisposable {
private readonly Bot Bot;
private readonly BoosterQueue BoosterQueue;
private Bot RespondingBot; // When we send status alerts, they'll come from this bot
private ulong RecipientSteamID; // When we send status alerts, they'll go to this SteamID
internal static ConcurrentDictionary<string, Timer> ResponseTimers = new();
internal List<string> StoredResponses = new();
private string LastResponse = "";
internal static ConcurrentDictionary<string, BoosterHandler> BoosterHandlers = new();
internal static StatusReporter GeneralReporter = new(); // Used to report when an unexpected booster error has occured, or when booster crafting is finished
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) {
Bot = bot;
BoosterQueue = new BoosterQueue(Bot, this);
RespondingBot = bot;
RecipientSteamID = Bot.Actions.GetFirstSteamMasterID();
BoosterQueue = new BoosterQueue(Bot);
GeneralReporter.Update(bot, bot.Actions.GetFirstSteamMasterID());
}

public void Dispose() {
BoosterQueue.Dispose();
MarketRepeatTimer?.Dispose();
}

internal static void AddHandler(Bot bot) {
Expand All @@ -48,10 +37,12 @@ internal static void AddHandler(Bot bot) {
}

internal static void UpdateBotDelays(int? delayInSeconds = null) {
if (DelayBetweenBots <= 0 && (delayInSeconds == null || delayInSeconds <= 0)) {
if (DelayBetweenBots == 0 && (delayInSeconds == null || delayInSeconds == 0)) {
return;
}

// This feature only exists because it existed in Outzzz's BoosterCreator plugin. I don't think it's all that useful.

// This assumes that the same bots will be used all of the time, with the same names, and all boosters will be
// crafted when they're scheduled to be crafted (no unexpected delays due to Steam downtime or insufficient gems).
// If all of these things are true then BoosterDelayBetweenBots should work as it's described in the README. If these
Expand All @@ -62,41 +53,45 @@ internal static void UpdateBotDelays(int? delayInSeconds = null) {
DelayBetweenBots = delayInSeconds ?? DelayBetweenBots;
List<string> botNames = BoosterHandlers.Keys.ToList<string>();
botNames.Sort();

foreach (KeyValuePair<string, BoosterHandler> kvp in BoosterHandlers) {
int index = botNames.IndexOf(kvp.Key);
kvp.Value.BoosterQueue.BoosterDelay = DelayBetweenBots * index;
}
}

internal string ScheduleBoosters(HashSet<uint> gameIDs, Bot respondingBot, ulong recipientSteamID) {
RespondingBot = respondingBot;
RecipientSteamID = recipientSteamID;
internal string ScheduleBoosters(HashSet<uint> gameIDs, StatusReporter craftingReporter) {
foreach (uint gameID in gameIDs) {
BoosterQueue.AddBooster(gameID, BoosterType.OneTime);
}
BoosterQueue.OnBoosterInfosUpdated -= ScheduleBoostersResponse;
BoosterQueue.OnBoosterInfosUpdated += ScheduleBoostersResponse;
BoosterQueue.Start();

return Commands.FormatBotResponse(Bot, String.Format(Strings.BoosterCreationStarting, gameIDs.Count));
}
void handler() {
try {
string? message = BoosterQueue.GetShortStatus();
if (message == null) {
craftingReporter.Report(Bot, Strings.BoostersUncraftable);

private void ScheduleBoostersResponse() {
BoosterQueue.OnBoosterInfosUpdated -= ScheduleBoostersResponse;
string? message = BoosterQueue.GetShortStatus();
if (message == null) {
PerpareStatusReport(Strings.BoostersUncraftable);
return;
}

return;
craftingReporter.Report(Bot, message);
} finally {
BoosterQueue.OnBoosterInfosUpdated -= handler;
}
}

PerpareStatusReport(message);
GeneralReporter.Update(craftingReporter);
BoosterQueue.OnBoosterInfosUpdated += handler;
BoosterQueue.Start();

return Commands.FormatBotResponse(Bot, String.Format(Strings.BoosterCreationStarting, gameIDs.Count));
}

internal void SchedulePermanentBoosters(HashSet<uint> gameIDs) {
foreach (uint gameID in gameIDs) {
BoosterQueue.AddBooster(gameID, BoosterType.Permanent);
}

BoosterQueue.Start();
}

Expand All @@ -122,60 +117,6 @@ internal string GetStatus(bool shortStatus = false) {

return Commands.FormatBotResponse(Bot, BoosterQueue.GetStatus());
}

internal void PerpareStatusReport(string message, bool suppressDuplicateMessages = false) {
if (suppressDuplicateMessages && LastResponse == message) {
return;
}

LastResponse = message;
// Could be that multiple bots will try to respond all at once individually. Start a timer, during which all messages will be logged and sent all together when the timer triggers.
if (StoredResponses.Count == 0) {
message = Commands.FormatBotResponse(Bot, message);
}
StoredResponses.Add(message);
if (!ResponseTimers.ContainsKey(RespondingBot.BotName)) {
ResponseTimers[RespondingBot.BotName] = new Timer(
async e => await SendStatusReport(RespondingBot, RecipientSteamID).ConfigureAwait(false),
null,
GetMillisecondsFromNow(DateTime.Now.AddSeconds(5)),
Timeout.Infinite
);
}
}

private static async Task SendStatusReport(Bot respondingBot, ulong recipientSteamID) {
if (!respondingBot.IsConnectedAndLoggedOn) {
ResponseTimers[respondingBot.BotName].Change(BoosterHandler.GetMillisecondsFromNow(DateTime.Now.AddSeconds(1)), Timeout.Infinite);

return;
}

ResponseTimers.TryRemove(respondingBot.BotName, out Timer? _);
List<string> messages = new List<string>();
List<string> botNames = BoosterHandlers.Keys.ToList<string>();
botNames.Sort();
foreach (string botName in botNames) {
if (BoosterHandlers[botName].StoredResponses.Count == 0
|| BoosterHandlers[botName].RespondingBot.BotName != respondingBot.BotName) {
continue;
}

messages.Add(String.Join(Environment.NewLine, BoosterHandlers[botName].StoredResponses));
if (BoosterHandlers[botName].StoredResponses.Count > 1) {
messages.Add("");
}
BoosterHandlers[botName].StoredResponses.Clear();
}

string message = String.Join(Environment.NewLine, messages);

if (recipientSteamID == 0 || !new SteamID(recipientSteamID).IsIndividualAccount) {
ASF.ArchiLogger.LogGenericInfo(message);
} else {
await respondingBot.SendMessage(recipientSteamID, message).ConfigureAwait(false);
}
}

internal uint GetGemsNeeded() {
if (BoosterQueue.GetAvailableGems() > BoosterQueue.GetGemsNeeded(BoosterType.Any, wasCrafted: false)) {
Expand All @@ -195,27 +136,6 @@ internal void OnGemsRecieved() {
BoosterQueue.Start();
}

internal bool StopMarketTimer() {
if (MarketRepeatTimer == null) {
return false;
}

MarketRepeatTimer.Change(Timeout.Infinite, Timeout.Infinite);
MarketRepeatTimer.Dispose();
MarketRepeatTimer = null;

return true;
}

internal void StartMarketTimer(uint minutes) {
StopMarketTimer();
MarketRepeatTimer = new Timer(async e => await MarketHandler.AcceptMarketConfirmations(Bot).ConfigureAwait(false),
null,
TimeSpan.FromMinutes(minutes),
TimeSpan.FromMinutes(minutes)
);
}

internal static bool IsCraftingOneTimeBoosters() {
return BoosterHandlers.Any(handler => handler.Value.BoosterQueue.GetNumBoosters(BoosterType.OneTime, wasCrafted: false) > 0);
}
Expand Down
Loading

0 comments on commit 1d8a793

Please sign in to comment.