diff --git a/src/Commands/AboutCommandGroup.cs b/src/Commands/AboutCommandGroup.cs
index c034907..8529d48 100644
--- a/src/Commands/AboutCommandGroup.cs
+++ b/src/Commands/AboutCommandGroup.cs
@@ -2,6 +2,7 @@
 using System.Text;
 using JetBrains.Annotations;
 using Octobot.Data;
+using Octobot.Extensions;
 using Octobot.Services;
 using Remora.Commands.Attributes;
 using Remora.Commands.Groups;
diff --git a/src/Commands/BanCommandGroup.cs b/src/Commands/BanCommandGroup.cs
index 010a9da..8b62858 100644
--- a/src/Commands/BanCommandGroup.cs
+++ b/src/Commands/BanCommandGroup.cs
@@ -2,6 +2,7 @@ using System.ComponentModel;
 using System.Text;
 using JetBrains.Annotations;
 using Octobot.Data;
+using Octobot.Extensions;
 using Octobot.Services;
 using Octobot.Services.Update;
 using Remora.Commands.Attributes;
@@ -53,7 +54,7 @@ public class BanCommandGroup : CommandGroup
     /// <param name="target">The user to ban.</param>
     /// <param name="duration">The duration for this ban. The user will be automatically unbanned after this duration.</param>
     /// <param name="reason">
-    ///     The reason for this ban. Must be encoded with <see cref="Extensions.EncodeHeader" /> when passed to
+    ///     The reason for this ban. Must be encoded with <see cref="StringExtensions.EncodeHeader" /> when passed to
     ///     <see cref="IDiscordRestGuildAPI.CreateGuildBanAsync" />.
     /// </param>
     /// <returns>
@@ -196,7 +197,7 @@ public class BanCommandGroup : CommandGroup
     /// </summary>
     /// <param name="target">The user to unban.</param>
     /// <param name="reason">
-    ///     The reason for this unban. Must be encoded with <see cref="Extensions.EncodeHeader" /> when passed to
+    ///     The reason for this unban. Must be encoded with <see cref="StringExtensions.EncodeHeader" /> when passed to
     ///     <see cref="IDiscordRestGuildAPI.RemoveGuildBanAsync" />.
     /// </param>
     /// <returns>
diff --git a/src/Commands/ClearCommandGroup.cs b/src/Commands/ClearCommandGroup.cs
index c963fdf..a6ac188 100644
--- a/src/Commands/ClearCommandGroup.cs
+++ b/src/Commands/ClearCommandGroup.cs
@@ -2,6 +2,7 @@ using System.ComponentModel;
 using System.Text;
 using JetBrains.Annotations;
 using Octobot.Data;
+using Octobot.Extensions;
 using Octobot.Services;
 using Remora.Commands.Attributes;
 using Remora.Commands.Groups;
diff --git a/src/Commands/Events/ErrorLoggingPostExecutionEvent.cs b/src/Commands/Events/ErrorLoggingPostExecutionEvent.cs
index 9000267..d6a66cc 100644
--- a/src/Commands/Events/ErrorLoggingPostExecutionEvent.cs
+++ b/src/Commands/Events/ErrorLoggingPostExecutionEvent.cs
@@ -1,5 +1,6 @@
 using JetBrains.Annotations;
 using Microsoft.Extensions.Logging;
+using Octobot.Extensions;
 using Remora.Discord.Commands.Contexts;
 using Remora.Discord.Commands.Services;
 using Remora.Results;
diff --git a/src/Commands/Events/LoggingPreparationErrorEvent.cs b/src/Commands/Events/LoggingPreparationErrorEvent.cs
index b3f9425..be48e74 100644
--- a/src/Commands/Events/LoggingPreparationErrorEvent.cs
+++ b/src/Commands/Events/LoggingPreparationErrorEvent.cs
@@ -1,5 +1,6 @@
 using JetBrains.Annotations;
 using Microsoft.Extensions.Logging;
+using Octobot.Extensions;
 using Remora.Discord.Commands.Contexts;
 using Remora.Discord.Commands.Services;
 using Remora.Results;
diff --git a/src/Commands/KickCommandGroup.cs b/src/Commands/KickCommandGroup.cs
index 2ee99a9..05552a2 100644
--- a/src/Commands/KickCommandGroup.cs
+++ b/src/Commands/KickCommandGroup.cs
@@ -1,6 +1,7 @@
 using System.ComponentModel;
 using JetBrains.Annotations;
 using Octobot.Data;
+using Octobot.Extensions;
 using Octobot.Services;
 using Remora.Commands.Attributes;
 using Remora.Commands.Groups;
@@ -49,7 +50,7 @@ public class KickCommandGroup : CommandGroup
     /// </summary>
     /// <param name="target">The member to kick.</param>
     /// <param name="reason">
-    ///     The reason for this kick. Must be encoded with <see cref="Extensions.EncodeHeader" /> when passed to
+    ///     The reason for this kick. Must be encoded with <see cref="StringExtensions.EncodeHeader" /> when passed to
     ///     <see cref="IDiscordRestGuildAPI.RemoveGuildMemberAsync" />.
     /// </param>
     /// <returns>
diff --git a/src/Commands/MuteCommandGroup.cs b/src/Commands/MuteCommandGroup.cs
index 4ec4c6c..50fe7a3 100644
--- a/src/Commands/MuteCommandGroup.cs
+++ b/src/Commands/MuteCommandGroup.cs
@@ -2,6 +2,7 @@ using System.ComponentModel;
 using System.Text;
 using JetBrains.Annotations;
 using Octobot.Data;
+using Octobot.Extensions;
 using Octobot.Services;
 using Octobot.Services.Update;
 using Remora.Commands.Attributes;
@@ -50,7 +51,7 @@ public class MuteCommandGroup : CommandGroup
     /// <param name="target">The member to mute.</param>
     /// <param name="duration">The duration for this mute. The member will be automatically unmuted after this duration.</param>
     /// <param name="reason">
-    ///     The reason for this mute. Must be encoded with <see cref="Extensions.EncodeHeader" /> when passed to
+    ///     The reason for this mute. Must be encoded with <see cref="StringExtensions.EncodeHeader" /> when passed to
     ///     <see cref="IDiscordRestGuildAPI.ModifyGuildMemberAsync" />.
     /// </param>
     /// <returns>
@@ -213,7 +214,7 @@ public class MuteCommandGroup : CommandGroup
     /// </summary>
     /// <param name="target">The member to unmute.</param>
     /// <param name="reason">
-    ///     The reason for this unmute. Must be encoded with <see cref="Extensions.EncodeHeader" /> when passed to
+    ///     The reason for this unmute. Must be encoded with <see cref="StringExtensions.EncodeHeader" /> when passed to
     ///     <see cref="IDiscordRestGuildAPI.ModifyGuildMemberAsync" />.
     /// </param>
     /// <returns>
diff --git a/src/Commands/PingCommandGroup.cs b/src/Commands/PingCommandGroup.cs
index a1b14bd..293fbff 100644
--- a/src/Commands/PingCommandGroup.cs
+++ b/src/Commands/PingCommandGroup.cs
@@ -1,6 +1,7 @@
 using System.ComponentModel;
 using JetBrains.Annotations;
 using Octobot.Data;
+using Octobot.Extensions;
 using Octobot.Services;
 using Remora.Commands.Attributes;
 using Remora.Commands.Groups;
diff --git a/src/Commands/RemindCommandGroup.cs b/src/Commands/RemindCommandGroup.cs
index 966b3ec..4a4f6a1 100644
--- a/src/Commands/RemindCommandGroup.cs
+++ b/src/Commands/RemindCommandGroup.cs
@@ -2,6 +2,7 @@ using System.ComponentModel;
 using System.Text;
 using JetBrains.Annotations;
 using Octobot.Data;
+using Octobot.Extensions;
 using Octobot.Services;
 using Remora.Commands.Attributes;
 using Remora.Commands.Groups;
diff --git a/src/Commands/SettingsCommandGroup.cs b/src/Commands/SettingsCommandGroup.cs
index fc4fbe7..317b5c8 100644
--- a/src/Commands/SettingsCommandGroup.cs
+++ b/src/Commands/SettingsCommandGroup.cs
@@ -4,6 +4,7 @@ using System.Text.Json.Nodes;
 using JetBrains.Annotations;
 using Octobot.Data;
 using Octobot.Data.Options;
+using Octobot.Extensions;
 using Octobot.Services;
 using Remora.Commands.Attributes;
 using Remora.Commands.Groups;
diff --git a/src/Commands/ToolsCommandGroup.cs b/src/Commands/ToolsCommandGroup.cs
index 92f1785..6c70c01 100644
--- a/src/Commands/ToolsCommandGroup.cs
+++ b/src/Commands/ToolsCommandGroup.cs
@@ -3,6 +3,7 @@ using System.Drawing;
 using System.Text;
 using JetBrains.Annotations;
 using Octobot.Data;
+using Octobot.Extensions;
 using Octobot.Services;
 using Remora.Commands.Attributes;
 using Remora.Commands.Groups;
diff --git a/src/Data/Options/SnowflakeOption.cs b/src/Data/Options/SnowflakeOption.cs
index 2150725..66ada96 100644
--- a/src/Data/Options/SnowflakeOption.cs
+++ b/src/Data/Options/SnowflakeOption.cs
@@ -1,5 +1,6 @@
 using System.Text.Json.Nodes;
 using System.Text.RegularExpressions;
+using Octobot.Extensions;
 using Remora.Discord.Extensions.Formatting;
 using Remora.Rest.Core;
 using Remora.Results;
diff --git a/src/Extensions.cs b/src/Extensions.cs
deleted file mode 100644
index 00d3d36..0000000
--- a/src/Extensions.cs
+++ /dev/null
@@ -1,366 +0,0 @@
-using System.Net;
-using System.Text;
-using DiffPlex.DiffBuilder.Model;
-using Microsoft.Extensions.Logging;
-using Remora.Discord.API;
-using Remora.Discord.API.Abstractions.Objects;
-using Remora.Discord.API.Objects;
-using Remora.Discord.Commands.Contexts;
-using Remora.Discord.Commands.Extensions;
-using Remora.Discord.Commands.Feedback.Services;
-using Remora.Discord.Extensions.Embeds;
-using Remora.Discord.Extensions.Formatting;
-using Remora.Rest.Core;
-using Remora.Results;
-
-namespace Octobot;
-
-public static class Extensions
-{
-    /// <summary>
-    ///     Adds a footer representing that an action was performed by a <paramref name="user" />.
-    /// </summary>
-    /// <param name="builder">The builder to add the footer to.</param>
-    /// <param name="user">The user that performed the action whose tag and avatar to use.</param>
-    /// <returns>The builder with the added footer.</returns>
-    public static EmbedBuilder WithActionFooter(this EmbedBuilder builder, IUser user)
-    {
-        var avatarUrlResult = CDN.GetUserAvatarUrl(user, imageSize: 256);
-        var avatarUrl = avatarUrlResult.IsSuccess
-            ? avatarUrlResult.Entity.AbsoluteUri
-            : CDN.GetDefaultUserAvatarUrl(user, imageSize: 256).Entity.AbsoluteUri;
-
-        return builder.WithFooter(
-            new EmbedFooter($"{Messages.IssuedBy}:\n{user.GetTag()}", avatarUrl));
-    }
-
-    /// <summary>
-    ///     Adds a title using the author field, making it smaller than using the title field.
-    /// </summary>
-    /// <param name="builder">The builder to add the small title to.</param>
-    /// <param name="text">The text of the small title.</param>
-    /// <param name="avatarSource">The user whose avatar to use in the small title.</param>
-    /// <returns>The builder with the added small title in the author field.</returns>
-    public static EmbedBuilder WithSmallTitle(
-        this EmbedBuilder builder, string text, IUser? avatarSource = null)
-    {
-        Uri? avatarUrl = null;
-        if (avatarSource is not null)
-        {
-            var avatarUrlResult = CDN.GetUserAvatarUrl(avatarSource, imageSize: 256);
-
-            avatarUrl = avatarUrlResult.IsSuccess
-                ? avatarUrlResult.Entity
-                : CDN.GetDefaultUserAvatarUrl(avatarSource, imageSize: 256).Entity;
-        }
-
-        builder.Author = new EmbedAuthorBuilder(text, iconUrl: avatarUrl?.AbsoluteUri);
-        return builder;
-    }
-
-    /// <summary>
-    ///     Adds a user avatar in the thumbnail field.
-    /// </summary>
-    /// <param name="builder">The builder to add the thumbnail to.</param>
-    /// <param name="avatarSource">The user whose avatar to use in the thumbnail field.</param>
-    /// <returns>The builder with the added avatar in the thumbnail field.</returns>
-    public static EmbedBuilder WithLargeUserAvatar(
-        this EmbedBuilder builder, IUser avatarSource)
-    {
-        var avatarUrlResult = CDN.GetUserAvatarUrl(avatarSource, imageSize: 256);
-        var avatarUrl = avatarUrlResult.IsSuccess
-            ? avatarUrlResult.Entity
-            : CDN.GetDefaultUserAvatarUrl(avatarSource, imageSize: 256).Entity;
-
-        return builder.WithThumbnailUrl(avatarUrl.AbsoluteUri);
-    }
-
-    /// <summary>
-    ///     Adds a guild icon in the thumbnail field.
-    /// </summary>
-    /// <param name="builder">The builder to add the thumbnail to.</param>
-    /// <param name="iconSource">The guild whose icon to use in the thumbnail field.</param>
-    /// <returns>The builder with the added icon in the thumbnail field.</returns>
-    public static EmbedBuilder WithLargeGuildIcon(
-        this EmbedBuilder builder, IGuild iconSource)
-    {
-        var iconUrlResult = CDN.GetGuildIconUrl(iconSource, imageSize: 256);
-        return iconUrlResult.IsSuccess
-            ? builder.WithThumbnailUrl(iconUrlResult.Entity.AbsoluteUri)
-            : builder;
-    }
-
-    /// <summary>
-    ///     Adds a guild banner in the image field.
-    /// </summary>
-    /// <param name="builder">The builder to add the image to.</param>
-    /// <param name="bannerSource">The guild whose banner to use in the image field.</param>
-    /// <returns>The builder with the added banner in the image field.</returns>
-    public static EmbedBuilder WithGuildBanner(
-        this EmbedBuilder builder, IGuild bannerSource)
-    {
-        return bannerSource.Banner is not null
-            ? builder.WithImageUrl(CDN.GetGuildBannerUrl(bannerSource).Entity.AbsoluteUri)
-            : builder;
-    }
-
-    /// <summary>
-    ///     Adds a footer representing that the action was performed in the <paramref name="guild" />.
-    /// </summary>
-    /// <param name="builder">The builder to add the footer to.</param>
-    /// <param name="guild">The guild whose name and icon to use.</param>
-    /// <returns>The builder with the added footer.</returns>
-    public static EmbedBuilder WithGuildFooter(this EmbedBuilder builder, IGuild guild)
-    {
-        var iconUrlResult = CDN.GetGuildIconUrl(guild, imageSize: 256);
-        var iconUrl = iconUrlResult.IsSuccess
-            ? iconUrlResult.Entity.AbsoluteUri
-            : default(Optional<string>);
-
-        return builder.WithFooter(new EmbedFooter(guild.Name, iconUrl));
-    }
-
-    /// <summary>
-    ///     Adds a title representing that the action happened in the <paramref name="guild" />.
-    /// </summary>
-    /// <param name="builder">The builder to add the title to.</param>
-    /// <param name="guild">The guild whose name and icon to use.</param>
-    /// <returns>The builder with the added title.</returns>
-    public static EmbedBuilder WithGuildTitle(this EmbedBuilder builder, IGuild guild)
-    {
-        var iconUrlResult = CDN.GetGuildIconUrl(guild, imageSize: 256);
-        var iconUrl = iconUrlResult.IsSuccess
-            ? iconUrlResult.Entity.AbsoluteUri
-            : null;
-
-        builder.Author = new EmbedAuthorBuilder(guild.Name, iconUrl: iconUrl);
-        return builder;
-    }
-
-    /// <summary>
-    ///     Adds a scheduled event's cover image.
-    /// </summary>
-    /// <param name="builder">The builder to add the image to.</param>
-    /// <param name="eventId">The ID of the scheduled event whose image to use.</param>
-    /// <param name="imageHashOptional">The Optional containing the image hash.</param>
-    /// <returns>The builder with the added cover image.</returns>
-    public static EmbedBuilder WithEventCover(
-        this EmbedBuilder builder, Snowflake eventId, Optional<IImageHash?> imageHashOptional)
-    {
-        if (!imageHashOptional.IsDefined(out var imageHash))
-        {
-            return builder;
-        }
-
-        var iconUrlResult = CDN.GetGuildScheduledEventCoverUrl(eventId, imageHash, imageSize: 1024);
-        return iconUrlResult.IsDefined(out var iconUrl) ? builder.WithImageUrl(iconUrl.AbsoluteUri) : builder;
-    }
-
-    /// <summary>
-    ///     Sanitizes a string for use in <see cref="Markdown.BlockCode(string)" /> by inserting zero-width spaces in between
-    ///     symbols used to format the string with block code.
-    /// </summary>
-    /// <param name="s">The string to sanitize.</param>
-    /// <returns>The sanitized string that can be safely used in <see cref="Markdown.BlockCode(string)" />.</returns>
-    private static string SanitizeForBlockCode(this string s)
-    {
-        return s.Replace("```", "​`​`​`​");
-    }
-
-    /// <summary>
-    ///     Sanitizes a string (see <see cref="SanitizeForBlockCode" />) and formats the string to use Markdown Block Code
-    ///     formatting with a specified
-    ///     language for syntax highlighting.
-    /// </summary>
-    /// <param name="s">The string to sanitize and format.</param>
-    /// <param name="language"></param>
-    /// <returns>
-    ///     The sanitized string formatted to use Markdown Block Code with a specified
-    ///     language for syntax highlighting.
-    /// </returns>
-    public static string InBlockCode(this string s, string language = "")
-    {
-        s = s.SanitizeForBlockCode();
-        return
-            $"```{language}\n{s.SanitizeForBlockCode()}{(s.EndsWith("`", StringComparison.Ordinal) || string.IsNullOrWhiteSpace(s) ? " " : "")}```";
-    }
-
-    public static string Localized(this string key)
-    {
-        return Messages.ResourceManager.GetString(key, Messages.Culture) ?? key;
-    }
-
-    /// <summary>
-    ///     Encodes a string to allow its transmission in request headers.
-    /// </summary>
-    /// <remarks>Used when encountering "Request headers must contain only ASCII characters".</remarks>
-    /// <param name="s">The string to encode.</param>
-    /// <returns>An encoded string with spaces kept intact.</returns>
-    public static string EncodeHeader(this string s)
-    {
-        return WebUtility.UrlEncode(s).Replace('+', ' ');
-    }
-
-    public static string AsMarkdown(this DiffPaneModel model)
-    {
-        var builder = new StringBuilder();
-        foreach (var line in model.Lines)
-        {
-            if (line.Type is ChangeType.Deleted)
-            {
-                builder.Append("-- ");
-            }
-
-            if (line.Type is ChangeType.Inserted)
-            {
-                builder.Append("++ ");
-            }
-
-            if (line.Type is not ChangeType.Imaginary)
-            {
-                builder.AppendLine(line.Text);
-            }
-        }
-
-        return InBlockCode(builder.ToString(), "diff");
-    }
-
-    public static string GetTag(this IUser user)
-    {
-        return user.Discriminator is 0000 ? $"@{user.Username}" : $"{user.Username}#{user.Discriminator:0000}";
-    }
-
-    public static Snowflake ToSnowflake(this ulong id)
-    {
-        return DiscordSnowflake.New(id);
-    }
-
-    public static TResult? MaxOrDefault<TSource, TResult>(
-        this IEnumerable<TSource> source, Func<TSource, TResult> selector)
-    {
-        var list = source.ToList();
-        return list.Any() ? list.Max(selector) : default;
-    }
-
-    public static bool TryGetContextIDs(
-        this ICommandContext context, out Snowflake guildId,
-        out Snowflake channelId, out Snowflake executorId)
-    {
-        channelId = default;
-        executorId = default;
-        return context.TryGetGuildID(out guildId)
-               && context.TryGetChannelID(out channelId)
-               && context.TryGetUserID(out executorId);
-    }
-
-    /// <summary>
-    ///     Checks whether this Snowflake has any value set.
-    /// </summary>
-    /// <param name="snowflake">The Snowflake to check.</param>
-    /// <returns>true if the Snowflake has no value set or it's set to 0, false otherwise.</returns>
-    public static bool Empty(this Snowflake snowflake)
-    {
-        return snowflake.Value is 0;
-    }
-
-    /// <summary>
-    ///     Checks whether this snowflake is empty (see <see cref="Empty" />) or it's equal to
-    ///     <paramref name="anotherSnowflake" />
-    /// </summary>
-    /// <param name="snowflake">The Snowflake to check for emptiness</param>
-    /// <param name="anotherSnowflake">The Snowflake to check for equality with <paramref name="snowflake" />.</param>
-    /// <returns>
-    ///     true if <paramref name="snowflake" /> is empty or is equal to <paramref name="anotherSnowflake" />, false
-    ///     otherwise.
-    /// </returns>
-    /// <seealso cref="Empty" />
-    public static bool EmptyOrEqualTo(this Snowflake snowflake, Snowflake anotherSnowflake)
-    {
-        return snowflake.Empty() || snowflake == anotherSnowflake;
-    }
-
-    public static async Task<Result> SendContextualEmbedResultAsync(
-        this FeedbackService feedback, Result<Embed> embedResult, CancellationToken ct = default)
-    {
-        if (!embedResult.IsDefined(out var embed))
-        {
-            return Result.FromError(embedResult);
-        }
-
-        return (Result)await feedback.SendContextualEmbedAsync(embed, ct: ct);
-    }
-
-    /// <summary>
-    ///     Checks if the <paramref name="result" /> has failed due to an error that has resulted from neither invalid user
-    ///     input nor the execution environment and logs the error using the provided <paramref name="logger" />.
-    /// </summary>
-    /// <remarks>
-    ///     This has special behavior for <see cref="ExceptionError" /> - its exception will be passed to the
-    ///     <paramref name="logger" />
-    /// </remarks>
-    /// <param name="logger">The logger to use.</param>
-    /// <param name="result">The Result whose error check.</param>
-    /// <param name="message">The message to use if this result has failed.</param>
-    public static void LogResult(this ILogger logger, IResult result, string? message = "")
-    {
-        if (result.IsSuccess || result.Error.IsUserOrEnvironmentError())
-        {
-            return;
-        }
-
-        if (result.Error is ExceptionError exe)
-        {
-            logger.LogError(exe.Exception, "{ErrorMessage}", message);
-            return;
-        }
-
-        logger.LogWarning("{UserMessage}\n{ResultErrorMessage}", message, result.Error.Message);
-    }
-
-    public static void AddIfFailed(this List<Result> list, Result result)
-    {
-        if (!result.IsSuccess)
-        {
-            list.Add(result);
-        }
-    }
-
-    /// <summary>
-    ///     Return an appropriate result for a list of failed results. The list must only contain failed results.
-    /// </summary>
-    /// <param name="list">The list of failed results.</param>
-    /// <returns>
-    ///     A successful result if the list is empty, the only Result in the list, or <see cref="AggregateError" />
-    ///     containing all results from the list.
-    /// </returns>
-    /// <exception cref="InvalidOperationException"></exception>
-    public static Result AggregateErrors(this List<Result> list)
-    {
-        return list.Count switch
-        {
-            0 => Result.FromSuccess(),
-            1 => list[0],
-            _ => new AggregateError(list.Cast<IResult>().ToArray())
-        };
-    }
-
-    public static Result TryGetExternalEventData(this IGuildScheduledEvent scheduledEvent, out DateTimeOffset endTime,
-        out string? location)
-    {
-        endTime = default;
-        location = default;
-        if (!scheduledEvent.EntityMetadata.AsOptional().IsDefined(out var metadata))
-        {
-            return new ArgumentNullError(nameof(scheduledEvent.EntityMetadata));
-        }
-
-        if (!metadata.Location.IsDefined(out location))
-        {
-            return new ArgumentNullError(nameof(metadata.Location));
-        }
-
-        return scheduledEvent.ScheduledEndTime.AsOptional().IsDefined(out endTime)
-            ? Result.FromSuccess()
-            : new ArgumentNullError(nameof(scheduledEvent.ScheduledEndTime));
-    }
-}
diff --git a/src/Extensions/CollectionExtensions.cs b/src/Extensions/CollectionExtensions.cs
new file mode 100644
index 0000000..5322d09
--- /dev/null
+++ b/src/Extensions/CollectionExtensions.cs
@@ -0,0 +1,40 @@
+using Remora.Results;
+
+namespace Octobot.Extensions;
+
+public static class CollectionExtensions
+{
+    public static TResult? MaxOrDefault<TSource, TResult>(
+        this IEnumerable<TSource> source, Func<TSource, TResult> selector)
+    {
+        var list = source.ToList();
+        return list.Any() ? list.Max(selector) : default;
+    }
+
+    public static void AddIfFailed(this List<Result> list, Result result)
+    {
+        if (!result.IsSuccess)
+        {
+            list.Add(result);
+        }
+    }
+
+    /// <summary>
+    ///     Return an appropriate result for a list of failed results. The list must only contain failed results.
+    /// </summary>
+    /// <param name="list">The list of failed results.</param>
+    /// <returns>
+    ///     A successful result if the list is empty, the only Result in the list, or <see cref="AggregateError" />
+    ///     containing all results from the list.
+    /// </returns>
+    /// <exception cref="InvalidOperationException"></exception>
+    public static Result AggregateErrors(this List<Result> list)
+    {
+        return list.Count switch
+        {
+            0 => Result.FromSuccess(),
+            1 => list[0],
+            _ => new AggregateError(list.Cast<IResult>().ToArray())
+        };
+    }
+}
diff --git a/src/Extensions/CommandContextExtensions.cs b/src/Extensions/CommandContextExtensions.cs
new file mode 100644
index 0000000..a0c02f2
--- /dev/null
+++ b/src/Extensions/CommandContextExtensions.cs
@@ -0,0 +1,19 @@
+using Remora.Discord.Commands.Contexts;
+using Remora.Discord.Commands.Extensions;
+using Remora.Rest.Core;
+
+namespace Octobot.Extensions;
+
+public static class CommandContextExtensions
+{
+    public static bool TryGetContextIDs(
+        this ICommandContext context, out Snowflake guildId,
+        out Snowflake channelId, out Snowflake executorId)
+    {
+        channelId = default;
+        executorId = default;
+        return context.TryGetGuildID(out guildId)
+               && context.TryGetChannelID(out channelId)
+               && context.TryGetUserID(out executorId);
+    }
+}
diff --git a/src/Extensions/DiffPaneModelExtensions.cs b/src/Extensions/DiffPaneModelExtensions.cs
new file mode 100644
index 0000000..ec7b8ee
--- /dev/null
+++ b/src/Extensions/DiffPaneModelExtensions.cs
@@ -0,0 +1,31 @@
+using System.Text;
+using DiffPlex.DiffBuilder.Model;
+
+namespace Octobot.Extensions;
+
+public static class DiffPaneModelExtensions
+{
+    public static string AsMarkdown(this DiffPaneModel model)
+    {
+        var builder = new StringBuilder();
+        foreach (var line in model.Lines)
+        {
+            if (line.Type is ChangeType.Deleted)
+            {
+                builder.Append("- ");
+            }
+
+            if (line.Type is ChangeType.Inserted)
+            {
+                builder.Append("+ ");
+            }
+
+            if (line.Type is not ChangeType.Imaginary)
+            {
+                builder.AppendLine(line.Text);
+            }
+        }
+
+        return builder.ToString().InBlockCode("diff");
+    }
+}
diff --git a/src/Extensions/EmbedBuilderExtensions.cs b/src/Extensions/EmbedBuilderExtensions.cs
new file mode 100644
index 0000000..2d61403
--- /dev/null
+++ b/src/Extensions/EmbedBuilderExtensions.cs
@@ -0,0 +1,149 @@
+using Remora.Discord.API;
+using Remora.Discord.API.Abstractions.Objects;
+using Remora.Discord.API.Objects;
+using Remora.Discord.Extensions.Embeds;
+using Remora.Rest.Core;
+
+namespace Octobot.Extensions;
+
+public static class EmbedBuilderExtensions
+{
+    /// <summary>
+    ///     Adds a footer representing that an action was performed by a <paramref name="user" />.
+    /// </summary>
+    /// <param name="builder">The builder to add the footer to.</param>
+    /// <param name="user">The user that performed the action whose tag and avatar to use.</param>
+    /// <returns>The builder with the added footer.</returns>
+    public static EmbedBuilder WithActionFooter(this EmbedBuilder builder, IUser user)
+    {
+        var avatarUrlResult = CDN.GetUserAvatarUrl(user, imageSize: 256);
+        var avatarUrl = avatarUrlResult.IsSuccess
+            ? avatarUrlResult.Entity.AbsoluteUri
+            : CDN.GetDefaultUserAvatarUrl(user, imageSize: 256).Entity.AbsoluteUri;
+
+        return builder.WithFooter(
+            new EmbedFooter($"{Messages.IssuedBy}:\n{user.GetTag()}", avatarUrl));
+    }
+
+    /// <summary>
+    ///     Adds a title using the author field, making it smaller than using the title field.
+    /// </summary>
+    /// <param name="builder">The builder to add the small title to.</param>
+    /// <param name="text">The text of the small title.</param>
+    /// <param name="avatarSource">The user whose avatar to use in the small title.</param>
+    /// <returns>The builder with the added small title in the author field.</returns>
+    public static EmbedBuilder WithSmallTitle(
+        this EmbedBuilder builder, string text, IUser? avatarSource = null)
+    {
+        Uri? avatarUrl = null;
+        if (avatarSource is not null)
+        {
+            var avatarUrlResult = CDN.GetUserAvatarUrl(avatarSource, imageSize: 256);
+
+            avatarUrl = avatarUrlResult.IsSuccess
+                ? avatarUrlResult.Entity
+                : CDN.GetDefaultUserAvatarUrl(avatarSource, imageSize: 256).Entity;
+        }
+
+        builder.Author = new EmbedAuthorBuilder(text, iconUrl: avatarUrl?.AbsoluteUri);
+        return builder;
+    }
+
+    /// <summary>
+    ///     Adds a user avatar in the thumbnail field.
+    /// </summary>
+    /// <param name="builder">The builder to add the thumbnail to.</param>
+    /// <param name="avatarSource">The user whose avatar to use in the thumbnail field.</param>
+    /// <returns>The builder with the added avatar in the thumbnail field.</returns>
+    public static EmbedBuilder WithLargeUserAvatar(
+        this EmbedBuilder builder, IUser avatarSource)
+    {
+        var avatarUrlResult = CDN.GetUserAvatarUrl(avatarSource, imageSize: 256);
+        var avatarUrl = avatarUrlResult.IsSuccess
+            ? avatarUrlResult.Entity
+            : CDN.GetDefaultUserAvatarUrl(avatarSource, imageSize: 256).Entity;
+
+        return builder.WithThumbnailUrl(avatarUrl.AbsoluteUri);
+    }
+
+    /// <summary>
+    ///     Adds a guild icon in the thumbnail field.
+    /// </summary>
+    /// <param name="builder">The builder to add the thumbnail to.</param>
+    /// <param name="iconSource">The guild whose icon to use in the thumbnail field.</param>
+    /// <returns>The builder with the added icon in the thumbnail field.</returns>
+    public static EmbedBuilder WithLargeGuildIcon(
+        this EmbedBuilder builder, IGuild iconSource)
+    {
+        var iconUrlResult = CDN.GetGuildIconUrl(iconSource, imageSize: 256);
+        return iconUrlResult.IsSuccess
+            ? builder.WithThumbnailUrl(iconUrlResult.Entity.AbsoluteUri)
+            : builder;
+    }
+
+    /// <summary>
+    ///     Adds a guild banner in the image field.
+    /// </summary>
+    /// <param name="builder">The builder to add the image to.</param>
+    /// <param name="bannerSource">The guild whose banner to use in the image field.</param>
+    /// <returns>The builder with the added banner in the image field.</returns>
+    public static EmbedBuilder WithGuildBanner(
+        this EmbedBuilder builder, IGuild bannerSource)
+    {
+        return bannerSource.Banner is not null
+            ? builder.WithImageUrl(CDN.GetGuildBannerUrl(bannerSource).Entity.AbsoluteUri)
+            : builder;
+    }
+
+    /// <summary>
+    ///     Adds a footer representing that the action was performed in the <paramref name="guild" />.
+    /// </summary>
+    /// <param name="builder">The builder to add the footer to.</param>
+    /// <param name="guild">The guild whose name and icon to use.</param>
+    /// <returns>The builder with the added footer.</returns>
+    public static EmbedBuilder WithGuildFooter(this EmbedBuilder builder, IGuild guild)
+    {
+        var iconUrlResult = CDN.GetGuildIconUrl(guild, imageSize: 256);
+        var iconUrl = iconUrlResult.IsSuccess
+            ? iconUrlResult.Entity.AbsoluteUri
+            : default(Optional<string>);
+
+        return builder.WithFooter(new EmbedFooter(guild.Name, iconUrl));
+    }
+
+    /// <summary>
+    ///     Adds a title representing that the action happened in the <paramref name="guild" />.
+    /// </summary>
+    /// <param name="builder">The builder to add the title to.</param>
+    /// <param name="guild">The guild whose name and icon to use.</param>
+    /// <returns>The builder with the added title.</returns>
+    public static EmbedBuilder WithGuildTitle(this EmbedBuilder builder, IGuild guild)
+    {
+        var iconUrlResult = CDN.GetGuildIconUrl(guild, imageSize: 256);
+        var iconUrl = iconUrlResult.IsSuccess
+            ? iconUrlResult.Entity.AbsoluteUri
+            : null;
+
+        builder.Author = new EmbedAuthorBuilder(guild.Name, iconUrl: iconUrl);
+        return builder;
+    }
+
+    /// <summary>
+    ///     Adds a scheduled event's cover image.
+    /// </summary>
+    /// <param name="builder">The builder to add the image to.</param>
+    /// <param name="eventId">The ID of the scheduled event whose image to use.</param>
+    /// <param name="imageHashOptional">The Optional containing the image hash.</param>
+    /// <returns>The builder with the added cover image.</returns>
+    public static EmbedBuilder WithEventCover(
+        this EmbedBuilder builder, Snowflake eventId, Optional<IImageHash?> imageHashOptional)
+    {
+        if (!imageHashOptional.IsDefined(out var imageHash))
+        {
+            return builder;
+        }
+
+        var iconUrlResult = CDN.GetGuildScheduledEventCoverUrl(eventId, imageHash, imageSize: 1024);
+        return iconUrlResult.IsDefined(out var iconUrl) ? builder.WithImageUrl(iconUrl.AbsoluteUri) : builder;
+    }
+}
diff --git a/src/Extensions/FeedbackServiceExtensions.cs b/src/Extensions/FeedbackServiceExtensions.cs
new file mode 100644
index 0000000..401a865
--- /dev/null
+++ b/src/Extensions/FeedbackServiceExtensions.cs
@@ -0,0 +1,19 @@
+using Remora.Discord.API.Objects;
+using Remora.Discord.Commands.Feedback.Services;
+using Remora.Results;
+
+namespace Octobot.Extensions;
+
+public static class FeedbackServiceExtensions
+{
+    public static async Task<Result> SendContextualEmbedResultAsync(
+        this FeedbackService feedback, Result<Embed> embedResult, CancellationToken ct = default)
+    {
+        if (!embedResult.IsDefined(out var embed))
+        {
+            return Result.FromError(embedResult);
+        }
+
+        return (Result)await feedback.SendContextualEmbedAsync(embed, ct: ct);
+    }
+}
diff --git a/src/Extensions/GuildScheduledEventExtensions.cs b/src/Extensions/GuildScheduledEventExtensions.cs
new file mode 100644
index 0000000..e3217e3
--- /dev/null
+++ b/src/Extensions/GuildScheduledEventExtensions.cs
@@ -0,0 +1,28 @@
+using Remora.Discord.API.Abstractions.Objects;
+using Remora.Rest.Core;
+using Remora.Results;
+
+namespace Octobot.Extensions;
+
+public static class GuildScheduledEventExtensions
+{
+    public static Result TryGetExternalEventData(this IGuildScheduledEvent scheduledEvent, out DateTimeOffset endTime,
+        out string? location)
+    {
+        endTime = default;
+        location = default;
+        if (!scheduledEvent.EntityMetadata.AsOptional().IsDefined(out var metadata))
+        {
+            return new ArgumentNullError(nameof(scheduledEvent.EntityMetadata));
+        }
+
+        if (!metadata.Location.IsDefined(out location))
+        {
+            return new ArgumentNullError(nameof(metadata.Location));
+        }
+
+        return scheduledEvent.ScheduledEndTime.AsOptional().IsDefined(out endTime)
+            ? Result.FromSuccess()
+            : new ArgumentNullError(nameof(scheduledEvent.ScheduledEndTime));
+    }
+}
diff --git a/src/Extensions/LoggerExtensions.cs b/src/Extensions/LoggerExtensions.cs
new file mode 100644
index 0000000..fd4aeb7
--- /dev/null
+++ b/src/Extensions/LoggerExtensions.cs
@@ -0,0 +1,35 @@
+using Microsoft.Extensions.Logging;
+using Remora.Discord.Commands.Extensions;
+using Remora.Results;
+
+namespace Octobot.Extensions;
+
+public static class LoggerExtensions
+{
+    /// <summary>
+    ///     Checks if the <paramref name="result" /> has failed due to an error that has resulted from neither invalid user
+    ///     input nor the execution environment and logs the error using the provided <paramref name="logger" />.
+    /// </summary>
+    /// <remarks>
+    ///     This has special behavior for <see cref="ExceptionError" /> - its exception will be passed to the
+    ///     <paramref name="logger" />
+    /// </remarks>
+    /// <param name="logger">The logger to use.</param>
+    /// <param name="result">The Result whose error check.</param>
+    /// <param name="message">The message to use if this result has failed.</param>
+    public static void LogResult(this ILogger logger, IResult result, string? message = "")
+    {
+        if (result.IsSuccess || result.Error.IsUserOrEnvironmentError())
+        {
+            return;
+        }
+
+        if (result.Error is ExceptionError exe)
+        {
+            logger.LogError(exe.Exception, "{ErrorMessage}", message);
+            return;
+        }
+
+        logger.LogWarning("{UserMessage}\n{ResultErrorMessage}", message, result.Error.Message);
+    }
+}
diff --git a/src/Extensions/SnowflakeExtensions.cs b/src/Extensions/SnowflakeExtensions.cs
new file mode 100644
index 0000000..e60bc44
--- /dev/null
+++ b/src/Extensions/SnowflakeExtensions.cs
@@ -0,0 +1,32 @@
+using Remora.Rest.Core;
+
+namespace Octobot.Extensions;
+
+public static class SnowflakeExtensions
+{
+    /// <summary>
+    ///     Checks whether this Snowflake has any value set.
+    /// </summary>
+    /// <param name="snowflake">The Snowflake to check.</param>
+    /// <returns>true if the Snowflake has no value set or it's set to 0, false otherwise.</returns>
+    public static bool Empty(this Snowflake snowflake)
+    {
+        return snowflake.Value is 0;
+    }
+
+    /// <summary>
+    ///     Checks whether this snowflake is empty (see <see cref="Empty" />) or it's equal to
+    ///     <paramref name="anotherSnowflake" />
+    /// </summary>
+    /// <param name="snowflake">The Snowflake to check for emptiness</param>
+    /// <param name="anotherSnowflake">The Snowflake to check for equality with <paramref name="snowflake" />.</param>
+    /// <returns>
+    ///     true if <paramref name="snowflake" /> is empty or is equal to <paramref name="anotherSnowflake" />, false
+    ///     otherwise.
+    /// </returns>
+    /// <seealso cref="Empty" />
+    public static bool EmptyOrEqualTo(this Snowflake snowflake, Snowflake anotherSnowflake)
+    {
+        return snowflake.Empty() || snowflake == anotherSnowflake;
+    }
+}
diff --git a/src/Extensions/StringExtensions.cs b/src/Extensions/StringExtensions.cs
new file mode 100644
index 0000000..3de05d3
--- /dev/null
+++ b/src/Extensions/StringExtensions.cs
@@ -0,0 +1,52 @@
+using System.Net;
+using Remora.Discord.Extensions.Formatting;
+
+namespace Octobot.Extensions;
+
+public static class StringExtensions
+{
+    /// <summary>
+    ///     Sanitizes a string for use in <see cref="Markdown.BlockCode(string)" /> by inserting zero-width spaces in between
+    ///     symbols used to format the string with block code.
+    /// </summary>
+    /// <param name="s">The string to sanitize.</param>
+    /// <returns>The sanitized string that can be safely used in <see cref="Markdown.BlockCode(string)" />.</returns>
+    private static string SanitizeForBlockCode(this string s)
+    {
+        return s.Replace("```", "​`​`​`​");
+    }
+
+    /// <summary>
+    ///     Sanitizes a string (see <see cref="SanitizeForBlockCode" />) and formats the string to use Markdown Block Code
+    ///     formatting with a specified
+    ///     language for syntax highlighting.
+    /// </summary>
+    /// <param name="s">The string to sanitize and format.</param>
+    /// <param name="language"></param>
+    /// <returns>
+    ///     The sanitized string formatted to use Markdown Block Code with a specified
+    ///     language for syntax highlighting.
+    /// </returns>
+    public static string InBlockCode(this string s, string language = "")
+    {
+        s = s.SanitizeForBlockCode();
+        return
+            $"```{language}\n{s.SanitizeForBlockCode()}{(s.EndsWith("`", StringComparison.Ordinal) || string.IsNullOrWhiteSpace(s) ? " " : "")}```";
+    }
+
+    public static string Localized(this string key)
+    {
+        return Messages.ResourceManager.GetString(key, Messages.Culture) ?? key;
+    }
+
+    /// <summary>
+    ///     Encodes a string to allow its transmission in request headers.
+    /// </summary>
+    /// <remarks>Used when encountering "Request headers must contain only ASCII characters".</remarks>
+    /// <param name="s">The string to encode.</param>
+    /// <returns>An encoded string with spaces kept intact.</returns>
+    public static string EncodeHeader(this string s)
+    {
+        return WebUtility.UrlEncode(s).Replace('+', ' ');
+    }
+}
diff --git a/src/Extensions/UInt64Extensions.cs b/src/Extensions/UInt64Extensions.cs
new file mode 100644
index 0000000..5d1db00
--- /dev/null
+++ b/src/Extensions/UInt64Extensions.cs
@@ -0,0 +1,12 @@
+using Remora.Discord.API;
+using Remora.Rest.Core;
+
+namespace Octobot.Extensions;
+
+public static class UInt64Extensions
+{
+    public static Snowflake ToSnowflake(this ulong id)
+    {
+        return DiscordSnowflake.New(id);
+    }
+}
diff --git a/src/Extensions/UserExtensions.cs b/src/Extensions/UserExtensions.cs
new file mode 100644
index 0000000..38fe985
--- /dev/null
+++ b/src/Extensions/UserExtensions.cs
@@ -0,0 +1,11 @@
+using Remora.Discord.API.Abstractions.Objects;
+
+namespace Octobot.Extensions;
+
+public static class UserExtensions
+{
+    public static string GetTag(this IUser user)
+    {
+        return user.Discriminator is 0000 ? $"@{user.Username}" : $"{user.Username}#{user.Discriminator:0000}";
+    }
+}
diff --git a/src/Responders/GuildLoadedResponder.cs b/src/Responders/GuildLoadedResponder.cs
index d78b69a..78dcc43 100644
--- a/src/Responders/GuildLoadedResponder.cs
+++ b/src/Responders/GuildLoadedResponder.cs
@@ -1,6 +1,7 @@
 using JetBrains.Annotations;
 using Microsoft.Extensions.Logging;
 using Octobot.Data;
+using Octobot.Extensions;
 using Octobot.Services;
 using Remora.Discord.API.Abstractions.Gateway.Events;
 using Remora.Discord.API.Abstractions.Rest;
diff --git a/src/Responders/GuildMemberJoinedResponder.cs b/src/Responders/GuildMemberJoinedResponder.cs
index 57bd01f..09075bf 100644
--- a/src/Responders/GuildMemberJoinedResponder.cs
+++ b/src/Responders/GuildMemberJoinedResponder.cs
@@ -1,6 +1,7 @@
 using System.Text.Json.Nodes;
 using JetBrains.Annotations;
 using Octobot.Data;
+using Octobot.Extensions;
 using Octobot.Services;
 using Remora.Discord.API.Abstractions.Gateway.Events;
 using Remora.Discord.API.Abstractions.Rest;
diff --git a/src/Responders/MessageDeletedResponder.cs b/src/Responders/MessageDeletedResponder.cs
index 9233820..5e4870b 100644
--- a/src/Responders/MessageDeletedResponder.cs
+++ b/src/Responders/MessageDeletedResponder.cs
@@ -1,6 +1,7 @@
 using System.Text;
 using JetBrains.Annotations;
 using Octobot.Data;
+using Octobot.Extensions;
 using Octobot.Services;
 using Remora.Discord.API.Abstractions.Gateway.Events;
 using Remora.Discord.API.Abstractions.Objects;
diff --git a/src/Responders/MessageEditedResponder.cs b/src/Responders/MessageEditedResponder.cs
index 5e2084c..7841b14 100644
--- a/src/Responders/MessageEditedResponder.cs
+++ b/src/Responders/MessageEditedResponder.cs
@@ -2,6 +2,7 @@ using System.Text;
 using DiffPlex.DiffBuilder;
 using JetBrains.Annotations;
 using Octobot.Data;
+using Octobot.Extensions;
 using Octobot.Services;
 using Remora.Discord.API.Abstractions.Gateway.Events;
 using Remora.Discord.API.Abstractions.Objects;
diff --git a/src/Services/Update/MemberUpdateService.cs b/src/Services/Update/MemberUpdateService.cs
index 6614273..ae099f6 100644
--- a/src/Services/Update/MemberUpdateService.cs
+++ b/src/Services/Update/MemberUpdateService.cs
@@ -2,6 +2,7 @@ using System.Text.RegularExpressions;
 using Microsoft.Extensions.Hosting;
 using Microsoft.Extensions.Logging;
 using Octobot.Data;
+using Octobot.Extensions;
 using Remora.Discord.API.Abstractions.Objects;
 using Remora.Discord.API.Abstractions.Rest;
 using Remora.Discord.Extensions.Embeds;
diff --git a/src/Services/Update/ScheduledEventUpdateService.cs b/src/Services/Update/ScheduledEventUpdateService.cs
index 7653d2b..20d23fa 100644
--- a/src/Services/Update/ScheduledEventUpdateService.cs
+++ b/src/Services/Update/ScheduledEventUpdateService.cs
@@ -2,6 +2,7 @@ using System.Text.Json.Nodes;
 using Microsoft.Extensions.Hosting;
 using Microsoft.Extensions.Logging;
 using Octobot.Data;
+using Octobot.Extensions;
 using Remora.Discord.API.Abstractions.Objects;
 using Remora.Discord.API.Abstractions.Rest;
 using Remora.Discord.API.Objects;
diff --git a/src/Services/UtilityService.cs b/src/Services/UtilityService.cs
index 22e38cb..b144ca7 100644
--- a/src/Services/UtilityService.cs
+++ b/src/Services/UtilityService.cs
@@ -3,6 +3,7 @@ using System.Text;
 using System.Text.Json.Nodes;
 using Microsoft.Extensions.Hosting;
 using Octobot.Data;
+using Octobot.Extensions;
 using Remora.Discord.API.Abstractions.Objects;
 using Remora.Discord.API.Abstractions.Rest;
 using Remora.Discord.Extensions.Embeds;