diff --git a/shared/commands/Modules/Render.cs b/shared/commands/Modules/Render.cs
index c89456b5..57d364a4 100644
--- a/shared/commands/Modules/Render.cs
+++ b/shared/commands/Modules/Render.cs
@@ -103,46 +103,49 @@ long id
_avatarSettings.RenderYDimension
);
- try {
+ try
+ {
- var (stream, fileName) = _avatarUtility.RenderUser(
- id,
- _avatarSettings.PlaceIdForRenders,
- _avatarSettings.RenderXDimension,
- _avatarSettings.RenderYDimension
- );
+ var (stream, fileName) = _avatarUtility.RenderUser(
+ id,
+ _avatarSettings.PlaceIdForRenders,
+ _avatarSettings.RenderXDimension,
+ _avatarSettings.RenderYDimension
+ );
- if (stream == null)
- {
- await FollowupAsync("An error occurred while rendering the character.");
+ if (stream == null)
+ {
+ await FollowupAsync("An error occurred while rendering the character.");
- return;
- }
+ return;
+ }
- using (stream)
- await FollowupWithFileAsync(
- stream,
- fileName
- );
+ using (stream)
+ await FollowupWithFileAsync(
+ stream,
+ fileName
+ );
}
- catch (Exception e)
+ catch (ThumbnailResponseException e)
{
- _logger.Error("An error occurred while rendering the character for the user '{0}': {1}", id, e);
+ _logger.Warning("The thumbnail service responded with the following state: {0}, message: {1}", e.State, e.Message);
- if (e is ThumbnailResponseException thumbnailResponseException)
+ if (e.State == ThumbnailResponseState.InReview)
{
- if (thumbnailResponseException.State == ThumbnailResponseState.InReview)
- {
- // Bogus error here for the sake of the user. Like flood checker error.
- await FollowupAsync("You are sending render commands too quickly, please wait a few moments and try again.");
-
- return;
- }
+ // Bogus error here for the sake of the user. Like flood checker error.
+ await FollowupAsync("The thumbnail service placed the request in review, please try again later.");
- // Bogus error for anything else, we don't need this to be noted that we are using rbx-thumbnails.
+ return;
}
+ // Bogus error for anything else, we don't need this to be noted that we are using rbx-thumbnails.
+ await FollowupAsync($"The thumbnail service responded with the following state: {e.State}");
+ }
+ catch (Exception e)
+ {
+ _logger.Error("An error occurred while rendering the character for the user '{0}': {1}", id, e);
+
await FollowupAsync("An error occurred while rendering the character.");
}
}
diff --git a/shared/settings/Providers/AvatarSettings.cs b/shared/settings/Providers/AvatarSettings.cs
index 9eb6fee6..6a7582f3 100644
--- a/shared/settings/Providers/AvatarSettings.cs
+++ b/shared/settings/Providers/AvatarSettings.cs
@@ -97,4 +97,25 @@ public class AvatarSettings : BaseSettingsProvider
nameof(LocalCacheTtl),
TimeSpan.FromMinutes(5)
);
+
+#if USE_VAULT_SETTINGS_PROVIDER
+ ///
+ /// A list of user IDs that should be automatically blacklisted.
+ ///
+ public long[] BlacklistUserIds {
+ get => GetOrDefault(
+ nameof(BlacklistUserIds),
+ Array.Empty()
+ );
+ set => Set(nameof(BlacklistUserIds), value);
+ }
+
+ ///
+ /// Gets the period to wait before persisting the blacklist.
+ ///
+ public TimeSpan BlacklistPersistPeriod => GetOrDefault(
+ nameof(BlacklistPersistPeriod),
+ TimeSpan.FromMinutes(5)
+ );
+#endif
}
diff --git a/shared/utility/Implementation/AvatarUtility.cs b/shared/utility/Implementation/AvatarUtility.cs
index 31a5de2e..75a1a713 100644
--- a/shared/utility/Implementation/AvatarUtility.cs
+++ b/shared/utility/Implementation/AvatarUtility.cs
@@ -16,6 +16,7 @@
using Grid.Commands;
using GridJob = Grid.Client.Job;
+using System.Collections.Concurrent;
///
/// Exception thrown when rbx-thumbnails returns a state that is not pending or completed.
@@ -54,6 +55,7 @@ public class AvatarUtility : IAvatarUtility
private readonly IPercentageInvoker _percentageInvoker;
private readonly ExpirableDictionary<(long, ThumbnailCommandType), string> _localCachedPaths;
+ private readonly ConcurrentBag _idsNotToUse = new(); // These IDs error out when trying to render (they are blocked? or they are moderated?)
///
/// Construct a new instance of .
@@ -82,7 +84,42 @@ IPercentageInvoker percentageInvoker
_localCachedPaths = new(avatarSettings.LocalCacheTtl);
_localCachedPaths.EntryRemoved += OnLocalCacheEntryRemoved;
+
+#if USE_VAULT_SETTINGS_PROVIDER
+ foreach (var id in avatarSettings.BlacklistUserIds)
+ _idsNotToUse.Add(id);
+
+ if (!_idsNotToUse.IsEmpty)
+ _logger.Warning("Blacklisted user IDs: {0}", string.Join(", ", avatarSettings.BlacklistUserIds));
+
+ Task.Factory.StartNew(PersistBlacklistedIds, TaskCreationOptions.LongRunning);
+#endif
+ }
+
+#if USE_VAULT_SETTINGS_PROVIDER
+ private void PersistBlacklistedIds()
+ {
+ while (true)
+ {
+ Task.Delay(_avatarSettings.BlacklistPersistPeriod).Wait();
+
+ if (_avatarSettings.BlacklistUserIds.SequenceEqual(_idsNotToUse))
+ continue;
+
+ // Logging purposes: grab any new ones that were added.
+ var newIds = _idsNotToUse.Except(_avatarSettings.BlacklistUserIds).ToArray();
+ var removedIds = _avatarSettings.BlacklistUserIds.Except(_idsNotToUse).ToArray();
+
+ _logger.Warning(
+ "Blacklisted user IDs were updated. New IDs: {0}, Removed IDs: {1}",
+ string.Join(", ", newIds),
+ string.Join(", ", removedIds)
+ );
+
+ _avatarSettings.BlacklistUserIds = [.. _idsNotToUse];
+ }
}
+#endif
private void OnLocalCacheEntryRemoved(string path, RemovalReason reason)
{
@@ -117,7 +154,7 @@ private IEnumerable