Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion DiscordPlayerCountBot/Bot/Bot.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ public class Bot : LoggableClass
public readonly Dictionary<DataProvider, IServerInformationProvider> DataProviders = [];
public readonly Dictionary<string, string> ApplicationTokens = [];
public string LastKnownStatus = string.Empty;
private DateTime _lastChannelNameUpdate = DateTime.MinValue;
private readonly TimeSpan _channelNameUpdateRateLimit = TimeSpan.FromMinutes(5);

public Bot(BotInformation info, Dictionary<string, string> applicationTokens, Dictionary<DataProvider, IServerInformationProvider> dataProviders)
{
Expand Down Expand Up @@ -134,6 +136,11 @@ public async Task UpdateAsync()
LastKnownStatus = serverInformation.ReplaceTagsWithValues(Information.StatusFormat, Information.UseNameAsLabel, Information.Name);

await DiscordClient.SetGameAsync(LastKnownStatus, null, (ActivityType)activityInteger);
await DiscordClient.SetChannelName(Information.ChannelID, LastKnownStatus);

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe that there is an interface and classes to be built here to support this. I think that we would want some sort of generic solution just in case Discord ever imposes a rate limit on any other part of the application.

public interface IRateLimiter
{
    /// <summary>
    /// Determines if the operation can be executed under current rate limits.
    /// </summary>
    bool CanExecute(string key);

    /// <summary>
    /// Records an operation attempt.
    /// </summary>
    void RecordExecution(string key);
}

The above is the interface that I think full encompasses what a rate limit would be.

As far as what is the implementation of what would use these objects I cannot say at this moment, I can only image in the short term, for the usage would be:

await obj.TryExecuteAsync("rename_channel", async () =>
{
    Console.WriteLine("Renaming channel..."); // Could do anything, not just log.
});

Maybe the key shouldn't be a string and should be an enum?

What are your thoughts?

if (DateTime.UtcNow - _lastChannelNameUpdate > _channelNameUpdateRateLimit)
{
await DiscordClient.SetChannelName(Information.ChannelID, LastKnownStatus);
_lastChannelNameUpdate = DateTime.UtcNow;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using DiscordPlayerCountBot.EnvironmentParser.Base;
using DiscordPlayerCountBot.EnvironmentParser.Base;

namespace DiscordPlayerCountBot.EnvironmentParser;

Expand All @@ -7,8 +7,22 @@ public class BotApplicationVariableParser : EnvironmentParserBase<Dictionary<str
public override string GetKey() => "BOT_APPLICATION_VARIABLES";
public override Dictionary<string, string> ParseTyped(string? environmentVariable)
{
return environmentVariable?.Split(";")
.Select(pair => pair.Split(','))
.ToDictionary(kv => kv[0], kv => kv[1]) ?? [];
if (string.IsNullOrEmpty(environmentVariable) || string.IsNullOrWhiteSpace(environmentVariable))
ArgumentException.ThrowIfNullOrEmpty(nameof(environmentVariable));

if (!environmentVariable!.Contains(';') && !environmentVariable.Contains(','))
throw new FormatException("The environment variable doesn't contain ';' and ','.");

if (!environmentVariable.Contains("SteamAPIKey", StringComparison.OrdinalIgnoreCase) && !environmentVariable.Contains("BattleMetricsKey", StringComparison.OrdinalIgnoreCase))
throw new FormatException("The input must contain either 'SteamAPIKey' or 'BattleMetricsKey'.");

if (!environmentVariable.Contains(','))
throw new FormatException("The environment variable must contain ','.");

return environmentVariable
.Split(';', StringSplitOptions.RemoveEmptyEntries)
.Select(pair => pair.Split(',', 2))
.Where(kv => kv.Length == 2)
.ToDictionary(kv => kv[0].Trim(), kv => kv[1].Trim());
}
}
}
14 changes: 14 additions & 0 deletions DiscordPlayerCountBot/UpdateController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,20 @@ public async Task LoadConfig()
Time = config.Item2;

Info($"Created: {Bots.Count} bot(s) that update every {Time} seconds.");

// Check for channel IDs set with update frequency less than 5 minutes
if (Time < 300)
{
foreach (var bot in Bots.Values)
{
if (bot.Information.ChannelID != null && bot.Information.ChannelID != 0)
{
Warn($"Bot '{bot.Information.Name}' has a channel ID set and update time is less than 5 minutes ({Time} seconds). " +
$"Channel name updates will be rate-limited to once every 5 minutes regardless of update timer to avoid Discord API throttling.",
bot.Information.Id.ToString());
}
}
}
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe it's best to do this warning message whenever we are running IConfigurable#Configure, as this is where we are populating and starting the bots.

foreach (var botName in botNames)
{
var address = botAddresses.ElementAtOrDefault(index);
var port = botPorts.ElementAtOrDefault(index);
var token = botTokens.ElementAtOrDefault(index) ?? throw new ApplicationException("Missing bot token.");
var status = botStatuses.ElementAtOrDefault(index);
var statusFormat = statusFormats.ElementAtOrDefault(index);
var nameAsLabel = botTags.ElementAtOrDefault(index);
var channelID = channelIds.ElementAtOrDefault(index);
var provider = EnumHelper.GetDataProvider(providerTypes.ElementAtOrDefault(index));
var info = new BotInformation()
{
Name = botName,
Address = string.Format("{0}:{1}", address, port),
Token = token,
Status = status,
StatusFormat = statusFormat,
UseNameAsLabel = nameAsLabel,
ChannelID = channelID,
ProviderType = provider
};
var bot = new Bot.Bot(info, applicationTokens, dataProviders);
await bot.StartAsync(shouldStart);
bots.Add(bot.Information.Id.ToString(), bot);
index++;
}

foreach (var info in config.ServerInformation)
{
var bot = new Bot.Bot(info, config.ApplicationTokens, dataProviders);
await bot.StartAsync(shouldStart);
bots.Add(bot.Information!.Id.ToString(), bot);
}

I believe the message is clear on what is going on; however, I do not like to concat strings like this and would rather use a string builder instead.

https://learn.microsoft.com/en-us/dotnet/api/system.text.stringbuilder?view=net-9.0

}

public async Task UpdatePlayerCounts()
Expand Down