diff --git a/TeamOctolings.Octobot/Commands/BanCommandGroup.cs b/TeamOctolings.Octobot/Commands/BanCommandGroup.cs
index 69be80f..db3a8ce 100644
--- a/TeamOctolings.Octobot/Commands/BanCommandGroup.cs
+++ b/TeamOctolings.Octobot/Commands/BanCommandGroup.cs
@@ -128,7 +128,7 @@ public sealed class BanCommandGroup : CommandGroup
         return await BanUserAsync(executor, target, reason, timeSpan, guild, data, channelId, bot, CancellationToken);
     }
 
-    private async Task<Result> BanUserAsync(
+    public async Task<Result> BanUserAsync(
         IUser executor, IUser target, string reason, TimeSpan? duration, IGuild guild, GuildData data,
         Snowflake channelId,
         IUser bot, CancellationToken ct = default)
diff --git a/TeamOctolings.Octobot/Commands/KickCommandGroup.cs b/TeamOctolings.Octobot/Commands/KickCommandGroup.cs
index a8fea2a..ccbe2a1 100644
--- a/TeamOctolings.Octobot/Commands/KickCommandGroup.cs
+++ b/TeamOctolings.Octobot/Commands/KickCommandGroup.cs
@@ -111,7 +111,7 @@ public sealed class KickCommandGroup : CommandGroup
         return await KickUserAsync(executor, target, reason, guild, channelId, data, bot, CancellationToken);
     }
 
-    private async Task<Result> KickUserAsync(
+    public async Task<Result> KickUserAsync(
         IUser executor, IUser target, string reason, IGuild guild, Snowflake channelId, GuildData data, IUser bot,
         CancellationToken ct = default)
     {
diff --git a/TeamOctolings.Octobot/Commands/MuteCommandGroup.cs b/TeamOctolings.Octobot/Commands/MuteCommandGroup.cs
index 46e8d84..6083e6e 100644
--- a/TeamOctolings.Octobot/Commands/MuteCommandGroup.cs
+++ b/TeamOctolings.Octobot/Commands/MuteCommandGroup.cs
@@ -123,7 +123,7 @@ public sealed class MuteCommandGroup : CommandGroup
             CancellationToken);
     }
 
-    private async Task<Result> MuteUserAsync(
+    public async Task<Result> MuteUserAsync(
         IUser executor, IUser target, string reason, TimeSpan duration, Snowflake guildId, GuildData data,
         Snowflake channelId, IUser bot, CancellationToken ct = default)
     {
diff --git a/TeamOctolings.Octobot/Commands/SettingsCommandGroup.cs b/TeamOctolings.Octobot/Commands/SettingsCommandGroup.cs
index 0acaa88..a03f20d 100644
--- a/TeamOctolings.Octobot/Commands/SettingsCommandGroup.cs
+++ b/TeamOctolings.Octobot/Commands/SettingsCommandGroup.cs
@@ -38,6 +38,7 @@ public sealed class SettingsCommandGroup : CommandGroup
     private static readonly IGuildOption[] AllOptions =
     [
         GuildSettings.Language,
+        GuildSettings.WarnPunishment,
         GuildSettings.WelcomeMessage,
         GuildSettings.LeaveMessage,
         GuildSettings.ReceiveStartupMessages,
@@ -45,6 +46,7 @@ public sealed class SettingsCommandGroup : CommandGroup
         GuildSettings.ReturnRolesOnRejoin,
         GuildSettings.AutoStartEvents,
         GuildSettings.RenameHoistedUsers,
+        GuildSettings.WarnsThreshold,
         GuildSettings.PublicFeedbackChannel,
         GuildSettings.PrivateFeedbackChannel,
         GuildSettings.WelcomeMessagesChannel,
@@ -53,7 +55,8 @@ public sealed class SettingsCommandGroup : CommandGroup
         GuildSettings.MuteRole,
         GuildSettings.ModeratorRole,
         GuildSettings.EventNotificationRole,
-        GuildSettings.EventEarlyNotificationOffset
+        GuildSettings.EventEarlyNotificationOffset,
+        GuildSettings.WarnPunishmentDuration
     ];
 
     private readonly ICommandContext _context;
diff --git a/TeamOctolings.Octobot/Commands/WarnCommandGroup.cs b/TeamOctolings.Octobot/Commands/WarnCommandGroup.cs
new file mode 100644
index 0000000..4b8d712
--- /dev/null
+++ b/TeamOctolings.Octobot/Commands/WarnCommandGroup.cs
@@ -0,0 +1,560 @@
+using System.Collections;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.Text;
+using System.Text.Json.Nodes;
+using JetBrains.Annotations;
+using Remora.Commands.Attributes;
+using Remora.Commands.Groups;
+using Remora.Discord.API.Abstractions.Objects;
+using Remora.Discord.API.Abstractions.Rest;
+using Remora.Discord.Commands.Attributes;
+using Remora.Discord.Commands.Conditions;
+using Remora.Discord.Commands.Contexts;
+using Remora.Discord.Commands.Feedback.Services;
+using Remora.Discord.Extensions.Embeds;
+using Remora.Discord.Extensions.Formatting;
+using Remora.Rest.Core;
+using Remora.Results;
+using TeamOctolings.Octobot.Data;
+using TeamOctolings.Octobot.Extensions;
+using TeamOctolings.Octobot.Services;
+using static System.DateTimeOffset;
+
+namespace TeamOctolings.Octobot.Commands;
+
+[UsedImplicitly]
+public class WarnCommandGroup : CommandGroup
+{
+    private readonly AccessControlService _access;
+    private readonly IDiscordRestChannelAPI _channelApi;
+    private readonly ICommandContext _context;
+    private readonly IFeedbackService _feedback;
+    private readonly IDiscordRestGuildAPI _guildApi;
+    private readonly GuildDataService _guildData;
+    private readonly IDiscordRestUserAPI _userApi;
+    private readonly Utility _utility;
+
+    public WarnCommandGroup(
+        ICommandContext context, IDiscordRestChannelAPI channelApi, GuildDataService guildData,
+        IFeedbackService feedback, IDiscordRestGuildAPI guildApi, IDiscordRestUserAPI userApi,
+        Utility utility, AccessControlService access)
+    {
+        _context = context;
+        _channelApi = channelApi;
+        _guildData = guildData;
+        _feedback = feedback;
+        _guildApi = guildApi;
+        _userApi = userApi;
+        _utility = utility;
+        _access = access;
+    }
+
+    [Command("warn")]
+    [DiscordDefaultMemberPermissions(DiscordPermission.ManageMessages)]
+    [DiscordDefaultDMPermission(false)]
+    [RequireContext(ChannelContext.Guild)]
+    [RequireDiscordPermission(DiscordPermission.ManageMessages)]
+    [RequireBotDiscordPermissions(DiscordPermission.KickMembers,
+        DiscordPermission.ModerateMembers, DiscordPermission.BanMembers)]
+    [Description("Warn user")]
+    [UsedImplicitly]
+    public async Task<Result> ExecuteWarnAsync(
+        [Description("User to warn")] IUser target,
+        [Description("Warn reason")] [MaxLength(256)]
+        string reason)
+    {
+        if (!_context.TryGetContextIDs(out var guildId, out var channelId, out var executorId))
+        {
+            return new ArgumentInvalidError(nameof(_context), "Unable to retrieve necessary IDs from command context");
+        }
+
+        var botResult = await _userApi.GetCurrentUserAsync(CancellationToken);
+        if (!botResult.IsDefined(out var bot))
+        {
+            return Result.FromError(botResult);
+        }
+
+        var executorResult = await _userApi.GetUserAsync(executorId, CancellationToken);
+        if (!executorResult.IsDefined(out var executor))
+        {
+            return Result.FromError(executorResult);
+        }
+
+        var guildResult = await _guildApi.GetGuildAsync(guildId, ct: CancellationToken);
+        if (!guildResult.IsDefined(out var guild))
+        {
+            return Result.FromError(guildResult);
+        }
+
+        var data = await _guildData.GetData(guild.ID, CancellationToken);
+        Messages.Culture = GuildSettings.Language.Get(data.Settings);
+
+        var interactionResult
+            = await _access.CheckInteractionsAsync(guild.ID, executor.ID, target.ID, "Warn", CancellationToken);
+        if (!interactionResult.IsSuccess)
+        {
+            return ResultExtensions.FromError(interactionResult);
+        }
+
+        if (interactionResult.Entity is not null)
+        {
+            var errorEmbed = new EmbedBuilder().WithSmallTitle(interactionResult.Entity, bot)
+                .WithColour(ColorsList.Red).Build();
+
+            return await _feedback.SendContextualEmbedResultAsync(errorEmbed, ct: CancellationToken);
+        }
+
+        return await WarnPreparationAsync(executor, target, reason, guild, data, channelId, bot, CancellationToken);
+    }
+
+    private async Task<Result> WarnPreparationAsync(IUser executor, IUser target, string reason, IGuild guild,
+        GuildData data, Snowflake channelId, IUser bot, CancellationToken ct = default)
+    {
+        var memberData = data.GetOrCreateMemberData(target.ID);
+        var warns = memberData.Warns;
+
+        var settings = data.Settings;
+
+        var warnThreshold = GuildSettings.WarnsThreshold.Get(settings);
+        var warnPunishment = GuildSettings.WarnPunishment.Get(settings);
+        var warnDuration = GuildSettings.WarnPunishmentDuration.Get(settings);
+
+        if (warnPunishment is "off" or "disable" or "disabled"
+            && warns.Count + 1 >= warnThreshold
+            && warnThreshold is not 0)
+        {
+            var errorEmbed = new EmbedBuilder()
+                .WithSmallTitle(string.Format(Messages.WarnThresholdExceeded, warnThreshold), bot)
+                .WithDescription(Messages.WarnThresholdExceededDescription)
+                .WithColour(ColorsList.Red).Build();
+
+            return await _feedback.SendContextualEmbedResultAsync(errorEmbed, ct: CancellationToken);
+        }
+
+        if (warnPunishment is "ban" or "mute" && warnDuration == TimeSpan.Zero)
+        {
+            var errorEmbed = new EmbedBuilder()
+                .WithSmallTitle(Messages.WarnPunishmentDurationNotSet, bot)
+                .WithColour(ColorsList.Red).Build();
+
+            return await _feedback.SendContextualEmbedResultAsync(errorEmbed, ct: CancellationToken);
+        }
+
+        if (warns.Count + 1 < warnThreshold || warnThreshold is 0)
+        {
+            return await WarnUserAsync(executor, target, reason, guild, data, channelId, bot, settings,
+                warns, warnThreshold, warnPunishment, warnDuration, ct);
+        }
+
+        var interactionResult
+            = await _access.CheckInteractionsAsync(guild.ID, bot.ID, target.ID,
+                $"{char.ToUpperInvariant(warnPunishment[0])}{warnPunishment[1..]}", ct);
+        if (!interactionResult.IsSuccess)
+        {
+            return ResultExtensions.FromError(interactionResult);
+        }
+
+        if (interactionResult.Entity is not null)
+        {
+            var errorEmbed = new EmbedBuilder().WithSmallTitle(interactionResult.Entity, bot)
+                .WithColour(ColorsList.Red).Build();
+
+            return await _feedback.SendContextualEmbedResultAsync(errorEmbed, ct: ct);
+        }
+
+        return await WarnUserAsync(executor, target, reason, guild, data, channelId, bot, settings,
+            warns, warnThreshold, warnPunishment, warnDuration, ct);
+    }
+
+    private async Task<Result> WarnUserAsync(IUser executor, IUser target, string reason, IGuild guild,
+        GuildData data, Snowflake channelId, IUser bot, JsonNode settings, IList warns, int warnThreshold,
+        string warnPunishment, TimeSpan warnDuration, CancellationToken ct = default)
+    {
+        warns.Add(new Warn
+        {
+            WarnedBy = executor.ID.Value,
+            At = UtcNow,
+            Reason = reason
+        });
+
+        var builder = new StringBuilder()
+            .AppendBulletPointLine(string.Format(Messages.DescriptionActionReason, reason))
+            .AppendBulletPointLine(string.Format(Messages.DescriptionWarns,
+                warnThreshold is 0 ? warns.Count : $"{warns.Count}/{warnThreshold}"));
+
+        var title = string.Format(Messages.UserWarned, target.GetTag());
+        var description = builder.ToString();
+
+        var dmChannelResult = await _userApi.CreateDMAsync(target.ID, ct);
+        if (dmChannelResult.IsDefined(out var dmChannel))
+        {
+            var dmEmbed = new EmbedBuilder().WithGuildTitle(guild)
+                .WithTitle(Messages.YouHaveBeenWarned)
+                .WithDescription(description)
+                .WithActionFooter(executor)
+                .WithCurrentTimestamp()
+                .WithColour(ColorsList.Yellow)
+                .Build();
+
+            await _channelApi.CreateMessageWithEmbedResultAsync(dmChannel.ID, embedResult: dmEmbed, ct: ct);
+        }
+
+        _utility.LogAction(settings, channelId, executor, title, description,
+            target, ColorsList.Yellow, false, ct);
+
+        var embed = new EmbedBuilder().WithSmallTitle(title, target)
+            .WithColour(ColorsList.Green).Build();
+
+        if (warns.Count >= warnThreshold && warnThreshold is not 0)
+        {
+            return await PunishUserAsync(target, guild, data, channelId, bot, warns, warnPunishment, warnDuration, CancellationToken);
+        }
+
+        return await _feedback.SendContextualEmbedResultAsync(embed, ct: ct);
+    }
+
+    private async Task<Result> PunishUserAsync(IUser target, IGuild guild, GuildData data,
+        Snowflake channelId, IUser bot, IList warns, string punishment, TimeSpan duration, CancellationToken ct)
+    {
+        if (punishment is "ban" && duration != TimeSpan.Zero)
+        {
+            var banCommandGroup = new BanCommandGroup(
+                _access, _channelApi, _context, _feedback, _guildApi, _guildData, _userApi, _utility);
+            await banCommandGroup.BanUserAsync(bot, target, Messages.ReceivedTooManyWarnings,
+                duration, guild, data, channelId, bot, ct);
+        }
+
+        if (punishment is "kick")
+        {
+            var kickCommandGroup = new KickCommandGroup(
+                _access, _channelApi, _context, _feedback, _guildApi, _guildData, _userApi, _utility);
+            await kickCommandGroup.KickUserAsync(bot, target, Messages.ReceivedTooManyWarnings,
+                guild, channelId, data, bot, ct);
+        }
+
+        if (punishment is "mute" && duration != TimeSpan.Zero)
+        {
+            var muteCommandGroup = new MuteCommandGroup(
+                _access, _context, _feedback, _guildApi, _guildData, _userApi, _utility);
+            await muteCommandGroup.MuteUserAsync(bot, target, Messages.ReceivedTooManyWarnings,
+                duration, guild.ID, data, channelId, bot, ct);
+        }
+
+        warns.Clear();
+        return Result.FromSuccess();
+    }
+
+    [Command("unwarn")]
+    [DiscordDefaultMemberPermissions(DiscordPermission.ManageMessages)]
+    [DiscordDefaultDMPermission(false)]
+    [RequireContext(ChannelContext.Guild)]
+    [RequireDiscordPermission(DiscordPermission.ManageMessages)]
+    [Description("Remove warns from user")]
+    [UsedImplicitly]
+    public async Task<Result> ExecuteUnwarnAsync(
+        [Description("User to remove warns from")]
+        IUser target,
+        [Description("Warn remove reason")] [MaxLength(256)]
+        string reason,
+        [Description("Number of the warning to be deleted")]
+        int? number = null)
+    {
+        if (!_context.TryGetContextIDs(out var guildId, out var channelId, out var executorId))
+        {
+            return new ArgumentInvalidError(nameof(_context), "Unable to retrieve necessary IDs from command context");
+        }
+
+        var botResult = await _userApi.GetCurrentUserAsync(CancellationToken);
+        if (!botResult.IsDefined(out var bot))
+        {
+            return Result.FromError(botResult);
+        }
+
+        var executorResult = await _userApi.GetUserAsync(executorId, CancellationToken);
+        if (!executorResult.IsDefined(out var executor))
+        {
+            return Result.FromError(executorResult);
+        }
+
+        var guildResult = await _guildApi.GetGuildAsync(guildId, ct: CancellationToken);
+        if (!guildResult.IsDefined(out var guild))
+        {
+            return Result.FromError(guildResult);
+        }
+
+        var data = await _guildData.GetData(guild.ID, CancellationToken);
+        Messages.Culture = GuildSettings.Language.Get(data.Settings);
+
+        var interactionResult
+            = await _access.CheckInteractionsAsync(guild.ID, executor.ID, target.ID, "Unwarn", CancellationToken);
+        if (!interactionResult.IsSuccess)
+        {
+            return ResultExtensions.FromError(interactionResult);
+        }
+
+        if (interactionResult.Entity is not null)
+        {
+            var errorEmbed = new EmbedBuilder().WithSmallTitle(interactionResult.Entity, bot)
+                .WithColour(ColorsList.Red).Build();
+
+            return await _feedback.SendContextualEmbedResultAsync(errorEmbed, ct: CancellationToken);
+        }
+
+        if (number is not null)
+        {
+            return await RemoveUserWarnAsync(executor, target, reason, number.Value, guild, data, channelId, bot,
+                CancellationToken);
+        }
+
+        return await RemoveUserWarnsAsync(executor, target, reason, guild, data, channelId, bot, CancellationToken);
+    }
+
+    private async Task<Result> RemoveUserWarnAsync(IUser executor, IUser target, string reason, int warnNumber,
+        IGuild guild, GuildData data, Snowflake channelId, IUser bot, CancellationToken ct = default)
+    {
+        var memberData = data.GetOrCreateMemberData(target.ID);
+        var warns = memberData.Warns;
+
+        if (warns.Count is 0)
+        {
+            var failedEmbed = new EmbedBuilder().WithSmallTitle(Messages.UserHasNoWarnings, bot)
+                .WithColour(ColorsList.Red).Build();
+
+            return await _feedback.SendContextualEmbedResultAsync(failedEmbed, ct: ct);
+        }
+
+        var index = warnNumber - 1;
+
+        if (index >= warns.Count || index < 0)
+        {
+            var failedEmbed = new EmbedBuilder().WithSmallTitle(Messages.WrongWarningNumberSelected, bot)
+                .WithColour(ColorsList.Red).Build();
+
+            return await _feedback.SendContextualEmbedResultAsync(failedEmbed, ct: ct);
+        }
+
+        var builder = new StringBuilder()
+            .Append("> ").AppendLine(warns[index].Reason)
+            .AppendBulletPointLine(string.Format(Messages.DescriptionActionReason, reason));
+
+        warns.RemoveAt(index);
+
+        var title = string.Format(Messages.UserWarnRemoved, warnNumber, target.GetTag());
+        var description = builder.ToString();
+
+        var dmChannelResult = await _userApi.CreateDMAsync(target.ID, ct);
+        if (dmChannelResult.IsDefined(out var dmChannel))
+        {
+            var dmEmbed = new EmbedBuilder().WithGuildTitle(guild)
+                .WithTitle(Messages.YourWarningHasBeenRevoked)
+                .WithDescription(description)
+                .WithActionFooter(executor)
+                .WithCurrentTimestamp()
+                .WithColour(ColorsList.Green)
+                .Build();
+
+            await _channelApi.CreateMessageWithEmbedResultAsync(dmChannel.ID, embedResult: dmEmbed, ct: ct);
+        }
+
+        var embed = new EmbedBuilder().WithSmallTitle(
+                title, target)
+            .WithColour(ColorsList.Green).Build();
+
+        _utility.LogAction(
+            data.Settings, channelId, executor, title, description, target, ColorsList.Yellow, false, ct);
+
+        return await _feedback.SendContextualEmbedResultAsync(embed, ct: ct);
+    }
+
+    private async Task<Result> RemoveUserWarnsAsync(IUser executor, IUser target, string reason,
+        IGuild guild, GuildData data, Snowflake channelId, IUser bot, CancellationToken ct = default)
+    {
+        var memberData = data.GetOrCreateMemberData(target.ID);
+        var warns = memberData.Warns;
+
+        if (warns.Count is 0)
+        {
+            var failedEmbed = new EmbedBuilder().WithSmallTitle(Messages.UserHasNoWarnings, bot)
+                .WithColour(ColorsList.Red).Build();
+
+            return await _feedback.SendContextualEmbedResultAsync(failedEmbed, ct: ct);
+        }
+
+        var builder = new StringBuilder().AppendBulletPointLine(string.Format(Messages.DescriptionActionReason, reason));
+
+        warns.Clear();
+
+        var title = string.Format(Messages.UserWarnsRemoved, target.GetTag());
+        var description = builder.ToString();
+
+        var dmChannelResult = await _userApi.CreateDMAsync(target.ID, ct);
+        if (dmChannelResult.IsDefined(out var dmChannel))
+        {
+            var dmEmbed = new EmbedBuilder().WithGuildTitle(guild)
+                .WithTitle(Messages.YourWarningsHaveBeenRevoked)
+                .WithDescription(description)
+                .WithActionFooter(executor)
+                .WithCurrentTimestamp()
+                .WithColour(ColorsList.Green)
+                .Build();
+
+            await _channelApi.CreateMessageWithEmbedResultAsync(dmChannel.ID, embedResult: dmEmbed, ct: ct);
+        }
+
+        var embed = new EmbedBuilder().WithSmallTitle(
+                title, target)
+            .WithColour(ColorsList.Green).Build();
+
+        _utility.LogAction(
+            data.Settings, channelId, executor, title, description, target, ColorsList.Yellow, false, ct);
+
+        return await _feedback.SendContextualEmbedResultAsync(embed, ct: ct);
+    }
+
+    [Command("listwarn")]
+    [DiscordDefaultDMPermission(false)]
+    [Ephemeral]
+    [Description("(Ephemeral) Get current warns")]
+    [UsedImplicitly]
+    public async Task<Result> ExecuteListWarnsAsync(
+        [Description("(Moderator-only) Get target's current warns")]
+        IUser? target = null)
+    {
+        if (!_context.TryGetContextIDs(out var guildId, out _, out var executorId))
+        {
+            return new ArgumentInvalidError(nameof(_context), "Unable to retrieve necessary IDs from command context");
+        }
+
+        var botResult = await _userApi.GetCurrentUserAsync(CancellationToken);
+        if (!botResult.IsDefined(out var bot))
+        {
+            return Result.FromError(botResult);
+        }
+
+        var executorResult = await _userApi.GetUserAsync(executorId, CancellationToken);
+        if (!executorResult.IsDefined(out var executor))
+        {
+            return Result.FromError(executorResult);
+        }
+
+        var guildResult = await _guildApi.GetGuildAsync(guildId, ct: CancellationToken);
+        if (!guildResult.IsDefined(out var guild))
+        {
+            return Result.FromError(guildResult);
+        }
+
+        var data = await _guildData.GetData(guild.ID, CancellationToken);
+        Messages.Culture = GuildSettings.Language.Get(data.Settings);
+
+        if (target is not null)
+        {
+            return await ListTargetWarnsAsync(executor, target, guild, data, bot, CancellationToken);
+        }
+
+        return await ListExecutorWarnsAsync(executor, data, bot, CancellationToken);
+    }
+
+    private async Task<Result> ListTargetWarnsAsync(IUser executor, IUser target, IGuild guild,
+        GuildData data, IUser bot, CancellationToken ct = default)
+    {
+        var interactionResult
+            = await _access.CheckInteractionsAsync(guild.ID, executor.ID, target.ID, "GetWarns", ct);
+        if (!interactionResult.IsSuccess)
+        {
+            return ResultExtensions.FromError(interactionResult);
+        }
+
+        if (interactionResult.Entity is not null)
+        {
+            var errorEmbed = new EmbedBuilder().WithSmallTitle(interactionResult.Entity, bot)
+                .WithColour(ColorsList.Red).Build();
+
+            return await _feedback.SendContextualEmbedResultAsync(errorEmbed, ct: ct);
+        }
+
+        var memberData = data.GetOrCreateMemberData(target.ID);
+        var warns = memberData.Warns;
+
+        if (warns.Count is 0)
+        {
+            var failedEmbed = new EmbedBuilder().WithSmallTitle(Messages.UserHasNoWarnings, bot)
+                .WithColour(ColorsList.Green).Build();
+
+            return await _feedback.SendContextualEmbedResultAsync(failedEmbed, ct: ct);
+        }
+
+        var warnThreshold = GuildSettings.WarnsThreshold.Get(data.Settings);
+
+        var punishmentType = GuildSettings.WarnPunishment.Get(data.Settings);
+
+        var description = new StringBuilder()
+            .AppendLine(string.Format(Messages.DescriptionWarns,
+                warnThreshold is 0 ? warns.Count : $"{warns.Count}/{warnThreshold}"));
+        if (punishmentType is not "off" and not "disable" and not "disabled")
+        {
+            description.AppendLine(string.Format(
+                Messages.DescriptionPunishmentType, Markdown.InlineCode(punishmentType)));
+        }
+
+        var warnCount = 0;
+        foreach (var warn in warns)
+        {
+            warnCount++;
+            description.Append(warnCount).Append(". ").AppendLine(warn.Reason)
+                .AppendSubBulletPoint(Messages.IssuedBy).Append(' ').AppendLine(Mention.User(warn.WarnedBy.ToSnowflake()))
+                .AppendSubBulletPointLine(string.Format(Messages.ReceivedOn, Markdown.Timestamp(warn.At)));
+        }
+
+        var embed = new EmbedBuilder()
+            .WithSmallTitle(string.Format(Messages.ListTargetWarnsTitle, target.GetTag()), target)
+            .WithDescription(description.ToString())
+            .WithColour(ColorsList.Default).Build();
+
+        return await _feedback.SendContextualEmbedResultAsync(embed, ct: ct);
+    }
+
+    private async Task<Result> ListExecutorWarnsAsync(IUser executor, GuildData data, IUser bot,
+        CancellationToken ct = default)
+    {
+        var memberData = data.GetOrCreateMemberData(executor.ID);
+        var warns = memberData.Warns;
+
+        if (warns.Count is 0)
+        {
+            var failedEmbed = new EmbedBuilder().WithSmallTitle(Messages.YouHaveNoWarnings, bot)
+                .WithColour(ColorsList.Green).Build();
+
+            return await _feedback.SendContextualEmbedResultAsync(failedEmbed, ct: ct);
+        }
+
+        var warnThreshold = GuildSettings.WarnsThreshold.Get(data.Settings);
+
+        var punishmentType = GuildSettings.WarnPunishment.Get(data.Settings);
+
+        var description = new StringBuilder()
+            .AppendLine(string.Format(Messages.DescriptionWarns,
+                warnThreshold is 0 ? warns.Count : $"{warns.Count}/{warnThreshold}"));
+        if (punishmentType is not "off" and not "disable" and not "disabled")
+        {
+            description.AppendLine(string.Format(
+                Messages.DescriptionPunishmentType, Markdown.InlineCode(punishmentType)));
+        }
+
+        var warnCount = 0;
+        foreach (var warn in warns)
+        {
+            warnCount++;
+            description.Append(warnCount).Append(". ").AppendLine(warn.Reason)
+                .AppendSubBulletPoint(Messages.IssuedBy).Append(' ').AppendLine(Mention.User(warn.WarnedBy.ToSnowflake()))
+                .AppendSubBulletPointLine(string.Format(Messages.ReceivedOn, Markdown.Timestamp(warn.At)));
+        }
+
+        var embed = new EmbedBuilder()
+            .WithSmallTitle(string.Format(Messages.ListExecutorWarnsTitle, executor.GetTag()), executor)
+            .WithDescription(description.ToString())
+            .WithColour(ColorsList.Default).Build();
+
+        return await _feedback.SendContextualEmbedResultAsync(embed, ct: ct);
+    }
+}
diff --git a/TeamOctolings.Octobot/Data/GuildSettings.cs b/TeamOctolings.Octobot/Data/GuildSettings.cs
index dc59d6f..26e325a 100644
--- a/TeamOctolings.Octobot/Data/GuildSettings.cs
+++ b/TeamOctolings.Octobot/Data/GuildSettings.cs
@@ -12,6 +12,8 @@ public static class GuildSettings
 {
     public static readonly LanguageOption Language = new("Language", "en");
 
+    public static readonly PunishmentOption WarnPunishment = new("WarnPunishment", "disabled");
+
     /// <summary>
     ///     Controls what message should be sent in <see cref="PublicFeedbackChannel" /> when a new member joins the guild.
     /// </summary>
@@ -58,6 +60,8 @@ public static class GuildSettings
     /// </summary>
     public static readonly BoolOption RenameHoistedUsers = new("RenameHoistedUsers", false);
 
+    public static readonly IntOption WarnsThreshold = new("WarnsThreshold", 0);
+
     /// <summary>
     ///     Controls what channel should all public messages be sent to.
     /// </summary>
@@ -84,4 +88,7 @@ public static class GuildSettings
     /// </summary>
     public static readonly TimeSpanOption EventEarlyNotificationOffset = new(
         "EventEarlyNotificationOffset", TimeSpan.Zero);
+
+    public static readonly TimeSpanOption WarnPunishmentDuration = new(
+        "WarnPunishmentDuration", TimeSpan.Zero);
 }
diff --git a/TeamOctolings.Octobot/Data/MemberData.cs b/TeamOctolings.Octobot/Data/MemberData.cs
index 984d4af..57b5cc9 100644
--- a/TeamOctolings.Octobot/Data/MemberData.cs
+++ b/TeamOctolings.Octobot/Data/MemberData.cs
@@ -5,13 +5,18 @@ namespace TeamOctolings.Octobot.Data;
 /// </summary>
 public sealed class MemberData
 {
-    public MemberData(ulong id, List<Reminder>? reminders = null)
+    public MemberData(ulong id, List<Reminder>? reminders = null, List<Warn>? warns = null)
     {
         Id = id;
         if (reminders is not null)
         {
             Reminders = reminders;
         }
+
+        if (warns is not null)
+        {
+            Warns = warns;
+        }
     }
 
     public ulong Id { get; }
@@ -20,4 +25,5 @@ public sealed class MemberData
     public bool Kicked { get; set; }
     public List<ulong> Roles { get; set; } = [];
     public List<Reminder> Reminders { get; } = [];
+    public List<Warn> Warns { get; } = [];
 }
diff --git a/TeamOctolings.Octobot/Data/Options/AllOptionsEnum.cs b/TeamOctolings.Octobot/Data/Options/AllOptionsEnum.cs
index 6a4280e..53b7f63 100644
--- a/TeamOctolings.Octobot/Data/Options/AllOptionsEnum.cs
+++ b/TeamOctolings.Octobot/Data/Options/AllOptionsEnum.cs
@@ -13,6 +13,7 @@ namespace TeamOctolings.Octobot.Data.Options;
 public enum AllOptionsEnum
 {
     [UsedImplicitly] Language,
+    [UsedImplicitly] WarnPunishment,
     [UsedImplicitly] WelcomeMessage,
     [UsedImplicitly] LeaveMessage,
     [UsedImplicitly] ReceiveStartupMessages,
@@ -20,6 +21,7 @@ public enum AllOptionsEnum
     [UsedImplicitly] ReturnRolesOnRejoin,
     [UsedImplicitly] AutoStartEvents,
     [UsedImplicitly] RenameHoistedUsers,
+    [UsedImplicitly] WarnsThreshold,
     [UsedImplicitly] PublicFeedbackChannel,
     [UsedImplicitly] PrivateFeedbackChannel,
     [UsedImplicitly] WelcomeMessagesChannel,
@@ -28,5 +30,6 @@ public enum AllOptionsEnum
     [UsedImplicitly] MuteRole,
     [UsedImplicitly] ModeratorRole,
     [UsedImplicitly] EventNotificationRole,
-    [UsedImplicitly] EventEarlyNotificationOffset
+    [UsedImplicitly] EventEarlyNotificationOffset,
+    [UsedImplicitly] WarnPunishmentDuration
 }
diff --git a/TeamOctolings.Octobot/Data/Options/IntOption.cs b/TeamOctolings.Octobot/Data/Options/IntOption.cs
new file mode 100644
index 0000000..3cdeb39
--- /dev/null
+++ b/TeamOctolings.Octobot/Data/Options/IntOption.cs
@@ -0,0 +1,31 @@
+using System.Text.Json.Nodes;
+using Remora.Results;
+
+namespace TeamOctolings.Octobot.Data.Options;
+
+public sealed class IntOption : GuildOption<int>
+{
+    public IntOption(string name, int defaultValue) : base(name, defaultValue) { }
+
+    public override string Display(JsonNode settings)
+    {
+        return settings[Name]?.GetValue<string>() ?? "0";
+    }
+
+    public override Result Set(JsonNode settings, string from)
+    {
+        if (!int.TryParse(from, out _))
+        {
+            return new ArgumentInvalidError(nameof(from), Messages.InvalidSettingValue);
+        }
+
+        settings[Name] = from;
+        return Result.FromSuccess();
+    }
+
+    public override int Get(JsonNode settings)
+    {
+        var property = settings[Name];
+        return property != null ? Convert.ToInt32(property.GetValue<string>()) : DefaultValue;
+    }
+}
diff --git a/TeamOctolings.Octobot/Data/Options/PunishmentOption.cs b/TeamOctolings.Octobot/Data/Options/PunishmentOption.cs
new file mode 100644
index 0000000..b65ee68
--- /dev/null
+++ b/TeamOctolings.Octobot/Data/Options/PunishmentOption.cs
@@ -0,0 +1,23 @@
+using System.Text.Json.Nodes;
+using Remora.Results;
+
+namespace TeamOctolings.Octobot.Data.Options;
+
+/// <inheritdoc />
+public sealed class PunishmentOption : GuildOption<string>
+{
+    private static readonly List<string> AllowedValues =
+    [
+        "ban", "kick", "mute", "off", "disable", "disabled"
+    ];
+
+    public PunishmentOption(string name, string defaultValue) : base(name, defaultValue) { }
+
+    /// <inheritdoc />
+    public override Result Set(JsonNode settings, string from)
+    {
+        return AllowedValues.Contains(from.ToLowerInvariant())
+            ? base.Set(settings, from.ToLowerInvariant())
+            : new ArgumentInvalidError(nameof(from), Messages.InvalidWarnPunishment);
+    }
+}
diff --git a/TeamOctolings.Octobot/Data/Warn.cs b/TeamOctolings.Octobot/Data/Warn.cs
new file mode 100644
index 0000000..ab512e6
--- /dev/null
+++ b/TeamOctolings.Octobot/Data/Warn.cs
@@ -0,0 +1,8 @@
+namespace TeamOctolings.Octobot.Data;
+
+public struct Warn
+{
+    public ulong WarnedBy { get; init; }
+    public DateTimeOffset At { get; init; }
+    public string Reason { get; init; }
+}
diff --git a/TeamOctolings.Octobot/Messages.Designer.cs b/TeamOctolings.Octobot/Messages.Designer.cs
index bbc1366..d56dc77 100644
--- a/TeamOctolings.Octobot/Messages.Designer.cs
+++ b/TeamOctolings.Octobot/Messages.Designer.cs
@@ -9,21 +9,21 @@
 
 namespace TeamOctolings.Octobot {
     using System;
-    
-    
+
+
     [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
     [System.Diagnostics.DebuggerNonUserCodeAttribute()]
     [System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
     internal class Messages {
-        
+
         private static System.Resources.ResourceManager resourceMan;
-        
+
         private static System.Globalization.CultureInfo resourceCulture;
-        
+
         [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
         internal Messages() {
         }
-        
+
         [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
         internal static System.Resources.ResourceManager ResourceManager {
             get {
@@ -34,7 +34,7 @@ namespace TeamOctolings.Octobot {
                 return resourceMan;
             }
         }
-        
+
         [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
         internal static System.Globalization.CultureInfo Culture {
             get {
@@ -44,1157 +44,1270 @@ namespace TeamOctolings.Octobot {
                 resourceCulture = value;
             }
         }
-        
+
         internal static string Ready {
             get {
                 return ResourceManager.GetString("Ready", resourceCulture);
             }
         }
-        
+
         internal static string CachedMessageDeleted {
             get {
                 return ResourceManager.GetString("CachedMessageDeleted", resourceCulture);
             }
         }
-        
+
         internal static string CachedMessageEdited {
             get {
                 return ResourceManager.GetString("CachedMessageEdited", resourceCulture);
             }
         }
-        
+
         internal static string DefaultWelcomeMessage {
             get {
                 return ResourceManager.GetString("DefaultWelcomeMessage", resourceCulture);
             }
         }
-        
+
         internal static string Generic1 {
             get {
                 return ResourceManager.GetString("Generic1", resourceCulture);
             }
         }
-        
+
         internal static string Generic2 {
             get {
                 return ResourceManager.GetString("Generic2", resourceCulture);
             }
         }
-        
+
         internal static string Generic3 {
             get {
                 return ResourceManager.GetString("Generic3", resourceCulture);
             }
         }
-        
+
         internal static string YouWereBanned {
             get {
                 return ResourceManager.GetString("YouWereBanned", resourceCulture);
             }
         }
-        
+
         internal static string PunishmentExpired {
             get {
                 return ResourceManager.GetString("PunishmentExpired", resourceCulture);
             }
         }
-        
+
         internal static string YouWereKicked {
             get {
                 return ResourceManager.GetString("YouWereKicked", resourceCulture);
             }
         }
-        
+
         internal static string Milliseconds {
             get {
                 return ResourceManager.GetString("Milliseconds", resourceCulture);
             }
         }
-        
+
         internal static string ChannelNotSpecified {
             get {
                 return ResourceManager.GetString("ChannelNotSpecified", resourceCulture);
             }
         }
-        
+
         internal static string RoleNotSpecified {
             get {
                 return ResourceManager.GetString("RoleNotSpecified", resourceCulture);
             }
         }
-        
+
         internal static string SettingsLanguage {
             get {
                 return ResourceManager.GetString("SettingsLanguage", resourceCulture);
             }
         }
-        
+
         internal static string SettingsPrefix {
             get {
                 return ResourceManager.GetString("SettingsPrefix", resourceCulture);
             }
         }
-        
+
         internal static string SettingsRemoveRolesOnMute {
             get {
                 return ResourceManager.GetString("SettingsRemoveRolesOnMute", resourceCulture);
             }
         }
-        
+
         internal static string SettingsSendWelcomeMessages {
             get {
                 return ResourceManager.GetString("SettingsSendWelcomeMessages", resourceCulture);
             }
         }
-        
+
         internal static string SettingsMuteRole {
             get {
                 return ResourceManager.GetString("SettingsMuteRole", resourceCulture);
             }
         }
-        
+
         internal static string LanguageNotSupported {
             get {
                 return ResourceManager.GetString("LanguageNotSupported", resourceCulture);
             }
         }
-        
+
         internal static string Yes {
             get {
                 return ResourceManager.GetString("Yes", resourceCulture);
             }
         }
-        
+
         internal static string No {
             get {
                 return ResourceManager.GetString("No", resourceCulture);
             }
         }
-        
+
         internal static string UserNotBanned {
             get {
                 return ResourceManager.GetString("UserNotBanned", resourceCulture);
             }
         }
-        
+
         internal static string MemberNotMuted {
             get {
                 return ResourceManager.GetString("MemberNotMuted", resourceCulture);
             }
         }
-        
+
         internal static string SettingsWelcomeMessage {
             get {
                 return ResourceManager.GetString("SettingsWelcomeMessage", resourceCulture);
             }
         }
-        
+
         internal static string UserBanned {
             get {
                 return ResourceManager.GetString("UserBanned", resourceCulture);
             }
         }
-        
+
         internal static string SettingsReceiveStartupMessages {
             get {
                 return ResourceManager.GetString("SettingsReceiveStartupMessages", resourceCulture);
             }
         }
-        
+
         internal static string InvalidSettingValue {
             get {
                 return ResourceManager.GetString("InvalidSettingValue", resourceCulture);
             }
         }
-        
+
         internal static string DurationRequiredForTimeOuts {
             get {
                 return ResourceManager.GetString("DurationRequiredForTimeOuts", resourceCulture);
             }
         }
-        
+
         internal static string CannotTimeOutBot {
             get {
                 return ResourceManager.GetString("CannotTimeOutBot", resourceCulture);
             }
         }
-        
+
         internal static string SettingsEventNotificationRole {
             get {
                 return ResourceManager.GetString("SettingsEventNotificationRole", resourceCulture);
             }
         }
-        
+
         internal static string SettingsEventNotificationChannel {
             get {
                 return ResourceManager.GetString("SettingsEventNotificationChannel", resourceCulture);
             }
         }
-        
+
         internal static string SettingsEventStartedReceivers {
             get {
                 return ResourceManager.GetString("SettingsEventStartedReceivers", resourceCulture);
             }
         }
-        
+
         internal static string EventStarted {
             get {
                 return ResourceManager.GetString("EventStarted", resourceCulture);
             }
         }
-        
+
         internal static string EventCancelled {
             get {
                 return ResourceManager.GetString("EventCancelled", resourceCulture);
             }
         }
-        
+
         internal static string EventCompleted {
             get {
                 return ResourceManager.GetString("EventCompleted", resourceCulture);
             }
         }
-        
+
         internal static string MessagesCleared {
             get {
                 return ResourceManager.GetString("MessagesCleared", resourceCulture);
             }
         }
-        
+
         internal static string SettingsNothingChanged {
             get {
                 return ResourceManager.GetString("SettingsNothingChanged", resourceCulture);
             }
         }
-        
+
         internal static string SettingNotDefined {
             get {
                 return ResourceManager.GetString("SettingNotDefined", resourceCulture);
             }
         }
-        
+
         internal static string MissingUser {
             get {
                 return ResourceManager.GetString("MissingUser", resourceCulture);
             }
         }
-        
+
         internal static string UserCannotBanMembers {
             get {
                 return ResourceManager.GetString("UserCannotBanMembers", resourceCulture);
             }
         }
-        
+
         internal static string UserCannotManageMessages {
             get {
                 return ResourceManager.GetString("UserCannotManageMessages", resourceCulture);
             }
         }
-        
+
         internal static string UserCannotKickMembers {
             get {
                 return ResourceManager.GetString("UserCannotKickMembers", resourceCulture);
             }
         }
-        
+
         internal static string UserCannotMuteMembers {
             get {
                 return ResourceManager.GetString("UserCannotMuteMembers", resourceCulture);
             }
         }
-        
+
         internal static string UserCannotUnmuteMembers {
             get {
                 return ResourceManager.GetString("UserCannotUnmuteMembers", resourceCulture);
             }
         }
-        
+
         internal static string UserCannotManageGuild {
             get {
                 return ResourceManager.GetString("UserCannotManageGuild", resourceCulture);
             }
         }
-        
+
         internal static string BotCannotBanMembers {
             get {
                 return ResourceManager.GetString("BotCannotBanMembers", resourceCulture);
             }
         }
-        
+
         internal static string BotCannotManageMessages {
             get {
                 return ResourceManager.GetString("BotCannotManageMessages", resourceCulture);
             }
         }
-        
+
         internal static string BotCannotKickMembers {
             get {
                 return ResourceManager.GetString("BotCannotKickMembers", resourceCulture);
             }
         }
-        
+
         internal static string BotCannotModerateMembers {
             get {
                 return ResourceManager.GetString("BotCannotModerateMembers", resourceCulture);
             }
         }
-        
+
         internal static string BotCannotManageGuild {
             get {
                 return ResourceManager.GetString("BotCannotManageGuild", resourceCulture);
             }
         }
-        
+
         internal static string UserCannotBanOwner {
             get {
                 return ResourceManager.GetString("UserCannotBanOwner", resourceCulture);
             }
         }
-        
+
         internal static string UserCannotBanThemselves {
             get {
                 return ResourceManager.GetString("UserCannotBanThemselves", resourceCulture);
             }
         }
-        
+
         internal static string UserCannotBanBot {
             get {
                 return ResourceManager.GetString("UserCannotBanBot", resourceCulture);
             }
         }
-        
+
         internal static string BotCannotBanTarget {
             get {
                 return ResourceManager.GetString("BotCannotBanTarget", resourceCulture);
             }
         }
-        
+
         internal static string UserCannotBanTarget {
             get {
                 return ResourceManager.GetString("UserCannotBanTarget", resourceCulture);
             }
         }
-        
+
         internal static string UserCannotKickOwner {
             get {
                 return ResourceManager.GetString("UserCannotKickOwner", resourceCulture);
             }
         }
-        
+
         internal static string UserCannotKickThemselves {
             get {
                 return ResourceManager.GetString("UserCannotKickThemselves", resourceCulture);
             }
         }
-        
+
         internal static string UserCannotKickBot {
             get {
                 return ResourceManager.GetString("UserCannotKickBot", resourceCulture);
             }
         }
-        
+
         internal static string BotCannotKickTarget {
             get {
                 return ResourceManager.GetString("BotCannotKickTarget", resourceCulture);
             }
         }
-        
+
         internal static string UserCannotKickTarget {
             get {
                 return ResourceManager.GetString("UserCannotKickTarget", resourceCulture);
             }
         }
-        
+
         internal static string UserCannotMuteOwner {
             get {
                 return ResourceManager.GetString("UserCannotMuteOwner", resourceCulture);
             }
         }
-        
+
         internal static string UserCannotMuteThemselves {
             get {
                 return ResourceManager.GetString("UserCannotMuteThemselves", resourceCulture);
             }
         }
-        
+
         internal static string UserCannotMuteBot {
             get {
                 return ResourceManager.GetString("UserCannotMuteBot", resourceCulture);
             }
         }
-        
+
         internal static string BotCannotMuteTarget {
             get {
                 return ResourceManager.GetString("BotCannotMuteTarget", resourceCulture);
             }
         }
-        
+
         internal static string UserCannotMuteTarget {
             get {
                 return ResourceManager.GetString("UserCannotMuteTarget", resourceCulture);
             }
         }
-        
+
         internal static string UserCannotUnmuteOwner {
             get {
                 return ResourceManager.GetString("UserCannotUnmuteOwner", resourceCulture);
             }
         }
-        
+
         internal static string UserCannotUnmuteThemselves {
             get {
                 return ResourceManager.GetString("UserCannotUnmuteThemselves", resourceCulture);
             }
         }
-        
+
         internal static string UserCannotUnmuteBot {
             get {
                 return ResourceManager.GetString("UserCannotUnmuteBot", resourceCulture);
             }
         }
-        
+
         internal static string BotCannotUnmuteTarget {
             get {
                 return ResourceManager.GetString("BotCannotUnmuteTarget", resourceCulture);
             }
         }
-        
+
         internal static string UserCannotUnmuteTarget {
             get {
                 return ResourceManager.GetString("UserCannotUnmuteTarget", resourceCulture);
             }
         }
-        
+
         internal static string EventEarlyNotification {
             get {
                 return ResourceManager.GetString("EventEarlyNotification", resourceCulture);
             }
         }
-        
+
         internal static string SettingsEventEarlyNotificationOffset {
             get {
                 return ResourceManager.GetString("SettingsEventEarlyNotificationOffset", resourceCulture);
             }
         }
-        
+
         internal static string UserNotFound {
             get {
                 return ResourceManager.GetString("UserNotFound", resourceCulture);
             }
         }
-        
+
         internal static string SettingsDefaultRole {
             get {
                 return ResourceManager.GetString("SettingsDefaultRole", resourceCulture);
             }
         }
-        
+
         internal static string SettingsPublicFeedbackChannel {
             get {
                 return ResourceManager.GetString("SettingsPublicFeedbackChannel", resourceCulture);
             }
         }
-        
+
         internal static string SettingsPrivateFeedbackChannel {
             get {
                 return ResourceManager.GetString("SettingsPrivateFeedbackChannel", resourceCulture);
             }
         }
-        
+
         internal static string SettingsReturnRolesOnRejoin {
             get {
                 return ResourceManager.GetString("SettingsReturnRolesOnRejoin", resourceCulture);
             }
         }
-        
+
         internal static string SettingsAutoStartEvents {
             get {
                 return ResourceManager.GetString("SettingsAutoStartEvents", resourceCulture);
             }
         }
-        
+
         internal static string IssuedBy {
             get {
                 return ResourceManager.GetString("IssuedBy", resourceCulture);
             }
         }
-        
+
         internal static string EventCreatedTitle {
             get {
                 return ResourceManager.GetString("EventCreatedTitle", resourceCulture);
             }
         }
-        
+
         internal static string DescriptionLocalEventCreated {
             get {
                 return ResourceManager.GetString("DescriptionLocalEventCreated", resourceCulture);
             }
         }
-        
+
         internal static string DescriptionExternalEventCreated {
             get {
                 return ResourceManager.GetString("DescriptionExternalEventCreated", resourceCulture);
             }
         }
-        
+
         internal static string ButtonOpenEventInfo {
             get {
                 return ResourceManager.GetString("ButtonOpenEventInfo", resourceCulture);
             }
         }
-        
+
         internal static string EventDuration {
             get {
                 return ResourceManager.GetString("EventDuration", resourceCulture);
             }
         }
-        
+
         internal static string DescriptionLocalEventStarted {
             get {
                 return ResourceManager.GetString("DescriptionLocalEventStarted", resourceCulture);
             }
         }
-        
+
         internal static string DescriptionExternalEventStarted {
             get {
                 return ResourceManager.GetString("DescriptionExternalEventStarted", resourceCulture);
             }
         }
-        
+
         internal static string UserAlreadyBanned {
             get {
                 return ResourceManager.GetString("UserAlreadyBanned", resourceCulture);
             }
         }
-        
+
         internal static string UserUnbanned {
             get {
                 return ResourceManager.GetString("UserUnbanned", resourceCulture);
             }
         }
-        
+
         internal static string UserMuted {
             get {
                 return ResourceManager.GetString("UserMuted", resourceCulture);
             }
         }
-        
+
         internal static string UserUnmuted {
             get {
                 return ResourceManager.GetString("UserUnmuted", resourceCulture);
             }
         }
-        
+
         internal static string UserNotMuted {
             get {
                 return ResourceManager.GetString("UserNotMuted", resourceCulture);
             }
         }
-        
+
         internal static string UserNotFoundShort {
             get {
                 return ResourceManager.GetString("UserNotFoundShort", resourceCulture);
             }
         }
-        
+
         internal static string UserKicked {
             get {
                 return ResourceManager.GetString("UserKicked", resourceCulture);
             }
         }
-        
+
         internal static string DescriptionActionReason {
             get {
                 return ResourceManager.GetString("DescriptionActionReason", resourceCulture);
             }
         }
-        
+
         internal static string DescriptionActionExpiresAt {
             get {
                 return ResourceManager.GetString("DescriptionActionExpiresAt", resourceCulture);
             }
         }
-        
+
         internal static string UserAlreadyMuted {
             get {
                 return ResourceManager.GetString("UserAlreadyMuted", resourceCulture);
             }
         }
-        
+
         internal static string MessageFrom {
             get {
                 return ResourceManager.GetString("MessageFrom", resourceCulture);
             }
         }
-        
+
         internal static string AboutTitleDevelopers {
             get {
                 return ResourceManager.GetString("AboutTitleDevelopers", resourceCulture);
             }
         }
-        
+
         internal static string ButtonOpenRepository {
             get {
                 return ResourceManager.GetString("ButtonOpenRepository", resourceCulture);
             }
         }
-        
+
         internal static string AboutBot {
             get {
                 return ResourceManager.GetString("AboutBot", resourceCulture);
             }
         }
-        
+
         internal static string AboutDeveloper_mctaylors {
             get {
                 return ResourceManager.GetString("AboutDeveloper@mctaylors", resourceCulture);
             }
         }
-        
+
         internal static string AboutDeveloper_Octol1ttle {
             get {
                 return ResourceManager.GetString("AboutDeveloper@Octol1ttle", resourceCulture);
             }
         }
-        
+
         internal static string AboutDeveloper_neroduckale {
             get {
                 return ResourceManager.GetString("AboutDeveloper@neroduckale", resourceCulture);
             }
         }
-        
+
         internal static string ReminderCreated {
             get {
                 return ResourceManager.GetString("ReminderCreated", resourceCulture);
             }
         }
-        
+
         internal static string Reminder {
             get {
                 return ResourceManager.GetString("Reminder", resourceCulture);
             }
         }
-        
+
         internal static string DescriptionReminder {
             get {
                 return ResourceManager.GetString("DescriptionReminder", resourceCulture);
             }
         }
-        
+
         internal static string SettingsListTitle {
             get {
                 return ResourceManager.GetString("SettingsListTitle", resourceCulture);
             }
         }
-        
+
         internal static string SettingSuccessfullyChanged {
             get {
                 return ResourceManager.GetString("SettingSuccessfullyChanged", resourceCulture);
             }
         }
-        
+
         internal static string SettingNotChanged {
             get {
                 return ResourceManager.GetString("SettingNotChanged", resourceCulture);
             }
         }
-        
+
         internal static string SettingIsNow {
             get {
                 return ResourceManager.GetString("SettingIsNow", resourceCulture);
             }
         }
-        
+
         internal static string SettingsRenameHoistedUsers {
             get {
                 return ResourceManager.GetString("SettingsRenameHoistedUsers", resourceCulture);
             }
         }
-        
+
         internal static string Page {
             get {
                 return ResourceManager.GetString("Page", resourceCulture);
             }
         }
-        
+
         internal static string PageNotFound {
             get {
                 return ResourceManager.GetString("PageNotFound", resourceCulture);
             }
         }
-        
+
         internal static string PagesAllowed {
             get {
                 return ResourceManager.GetString("PagesAllowed", resourceCulture);
             }
         }
-        
+
         internal static string Next {
             get {
                 return ResourceManager.GetString("Next", resourceCulture);
             }
         }
-        
+
         internal static string Previous {
             get {
                 return ResourceManager.GetString("Previous", resourceCulture);
             }
         }
-        
+
         internal static string ReminderList {
             get {
                 return ResourceManager.GetString("ReminderList", resourceCulture);
             }
         }
-        
+
         internal static string InvalidReminderPosition {
             get {
                 return ResourceManager.GetString("InvalidReminderPosition", resourceCulture);
             }
         }
-        
+
         internal static string ReminderDeleted {
             get {
                 return ResourceManager.GetString("ReminderDeleted", resourceCulture);
             }
         }
-        
+
         internal static string NoRemindersFound {
             get {
                 return ResourceManager.GetString("NoRemindersFound", resourceCulture);
             }
         }
-        
+
         internal static string SingleSettingReset {
             get {
                 return ResourceManager.GetString("SingleSettingReset", resourceCulture);
             }
         }
-        
+
         internal static string AllSettingsReset {
             get {
                 return ResourceManager.GetString("AllSettingsReset", resourceCulture);
             }
         }
-        
+
         internal static string DescriptionActionJumpToMessage {
             get {
                 return ResourceManager.GetString("DescriptionActionJumpToMessage", resourceCulture);
             }
         }
-        
+
         internal static string DescriptionActionJumpToChannel {
             get {
                 return ResourceManager.GetString("DescriptionActionJumpToChannel", resourceCulture);
             }
         }
-        
+
         internal static string ReminderPosition {
             get {
                 return ResourceManager.GetString("ReminderPosition", resourceCulture);
             }
         }
-        
+
         internal static string ReminderTime {
             get {
                 return ResourceManager.GetString("ReminderTime", resourceCulture);
             }
         }
-        
+
         internal static string ReminderText {
             get {
                 return ResourceManager.GetString("ReminderText", resourceCulture);
             }
         }
-        
+
         internal static string UserInfoDisplayName {
             get {
                 return ResourceManager.GetString("UserInfoDisplayName", resourceCulture);
             }
         }
-        
+
         internal static string InformationAbout {
             get {
                 return ResourceManager.GetString("InformationAbout", resourceCulture);
             }
         }
-        
+
         internal static string UserInfoMuted {
             get {
                 return ResourceManager.GetString("UserInfoMuted", resourceCulture);
             }
         }
-        
+
         internal static string UserInfoDiscordUserSince {
             get {
                 return ResourceManager.GetString("UserInfoDiscordUserSince", resourceCulture);
             }
         }
-        
+
         internal static string UserInfoBanned {
             get {
                 return ResourceManager.GetString("UserInfoBanned", resourceCulture);
             }
         }
-        
+
         internal static string UserInfoPunishments {
             get {
                 return ResourceManager.GetString("UserInfoPunishments", resourceCulture);
             }
         }
-        
+
         internal static string UserInfoBannedPermanently {
             get {
                 return ResourceManager.GetString("UserInfoBannedPermanently", resourceCulture);
             }
         }
-        
+
         internal static string UserInfoNotOnGuild {
             get {
                 return ResourceManager.GetString("UserInfoNotOnGuild", resourceCulture);
             }
         }
-        
+
         internal static string UserInfoMutedByTimeout {
             get {
                 return ResourceManager.GetString("UserInfoMutedByTimeout", resourceCulture);
             }
         }
-        
+
         internal static string UserInfoMutedByMuteRole {
             get {
                 return ResourceManager.GetString("UserInfoMutedByMuteRole", resourceCulture);
             }
         }
-        
+
         internal static string UserInfoGuildMemberSince {
             get {
                 return ResourceManager.GetString("UserInfoGuildMemberSince", resourceCulture);
             }
         }
-        
+
         internal static string UserInfoGuildNickname {
             get {
                 return ResourceManager.GetString("UserInfoGuildNickname", resourceCulture);
             }
         }
-        
+
         internal static string UserInfoGuildRoles {
             get {
                 return ResourceManager.GetString("UserInfoGuildRoles", resourceCulture);
             }
         }
-        
+
         internal static string UserInfoGuildMemberPremiumSince {
             get {
                 return ResourceManager.GetString("UserInfoGuildMemberPremiumSince", resourceCulture);
             }
         }
-        
+
         internal static string RandomTitle {
             get {
                 return ResourceManager.GetString("RandomTitle", resourceCulture);
             }
         }
-        
+
         internal static string RandomMinMaxSame {
             get {
                 return ResourceManager.GetString("RandomMinMaxSame", resourceCulture);
             }
         }
-        
+
         internal static string RandomMin {
             get {
                 return ResourceManager.GetString("RandomMin", resourceCulture);
             }
         }
-        
+
         internal static string RandomMax {
             get {
                 return ResourceManager.GetString("RandomMax", resourceCulture);
             }
         }
-        
+
         internal static string Default {
             get {
                 return ResourceManager.GetString("Default", resourceCulture);
             }
         }
-        
+
         internal static string TimestampTitle {
             get {
                 return ResourceManager.GetString("TimestampTitle", resourceCulture);
             }
         }
-        
+
         internal static string TimestampOffset {
             get {
                 return ResourceManager.GetString("TimestampOffset", resourceCulture);
             }
         }
-        
+
         internal static string GuildInfoDescription {
             get {
                 return ResourceManager.GetString("GuildInfoDescription", resourceCulture);
             }
         }
-        
+
         internal static string GuildInfoCreatedAt {
             get {
                 return ResourceManager.GetString("GuildInfoCreatedAt", resourceCulture);
             }
         }
-        
+
         internal static string GuildInfoOwner {
             get {
                 return ResourceManager.GetString("GuildInfoOwner", resourceCulture);
             }
         }
-        
+
         internal static string GuildInfoServerBoost {
             get {
                 return ResourceManager.GetString("GuildInfoServerBoost", resourceCulture);
             }
         }
-        
+
         internal static string GuildInfoBoostTier {
             get {
                 return ResourceManager.GetString("GuildInfoBoostTier", resourceCulture);
             }
         }
-        
+
         internal static string GuildInfoBoostCount {
             get {
                 return ResourceManager.GetString("GuildInfoBoostCount", resourceCulture);
             }
         }
-        
+
         internal static string NoMessagesToClear {
             get {
                 return ResourceManager.GetString("NoMessagesToClear", resourceCulture);
             }
         }
-        
+
         internal static string MessagesClearedFiltered {
             get {
                 return ResourceManager.GetString("MessagesClearedFiltered", resourceCulture);
             }
         }
-        
+
         internal static string DataLoadFailedTitle {
             get {
                 return ResourceManager.GetString("DataLoadFailedTitle", resourceCulture);
             }
         }
-        
+
         internal static string DataLoadFailedDescription {
             get {
                 return ResourceManager.GetString("DataLoadFailedDescription", resourceCulture);
             }
         }
-        
+
         internal static string CommandExecutionFailed {
             get {
                 return ResourceManager.GetString("CommandExecutionFailed", resourceCulture);
             }
         }
-        
+
         internal static string ContactDevelopers {
             get {
                 return ResourceManager.GetString("ContactDevelopers", resourceCulture);
             }
         }
-        
+
         internal static string ButtonReportIssue {
             get {
                 return ResourceManager.GetString("ButtonReportIssue", resourceCulture);
             }
         }
-        
+
         internal static string DefaultLeaveMessage {
             get {
                 return ResourceManager.GetString("DefaultLeaveMessage", resourceCulture);
             }
         }
-        
+
         internal static string SettingsLeaveMessage {
             get {
                 return ResourceManager.GetString("SettingsLeaveMessage", resourceCulture);
             }
         }
-        
+
         internal static string InvalidTimeSpan {
             get {
                 return ResourceManager.GetString("InvalidTimeSpan", resourceCulture);
             }
         }
-        
+
         internal static string UserInfoKicked {
             get {
                 return ResourceManager.GetString("UserInfoKicked", resourceCulture);
             }
         }
-        
+
         internal static string ReminderEdited {
             get {
                 return ResourceManager.GetString("ReminderEdited", resourceCulture);
             }
         }
-        
+
         internal static string EightBallPositive1 {
             get {
                 return ResourceManager.GetString("EightBallPositive1", resourceCulture);
             }
         }
-        
+
         internal static string EightBallPositive2 {
             get {
                 return ResourceManager.GetString("EightBallPositive2", resourceCulture);
             }
         }
-        
+
         internal static string EightBallPositive3 {
             get {
                 return ResourceManager.GetString("EightBallPositive3", resourceCulture);
             }
         }
-        
+
         internal static string EightBallPositive4 {
             get {
                 return ResourceManager.GetString("EightBallPositive4", resourceCulture);
             }
         }
-        
+
         internal static string EightBallPositive5 {
             get {
                 return ResourceManager.GetString("EightBallPositive5", resourceCulture);
             }
         }
-        
+
         internal static string EightBallQuestionable1 {
             get {
                 return ResourceManager.GetString("EightBallQuestionable1", resourceCulture);
             }
         }
-        
+
         internal static string EightBallQuestionable2 {
             get {
                 return ResourceManager.GetString("EightBallQuestionable2", resourceCulture);
             }
         }
-        
+
         internal static string EightBallQuestionable3 {
             get {
                 return ResourceManager.GetString("EightBallQuestionable3", resourceCulture);
             }
         }
-        
+
         internal static string EightBallQuestionable4 {
             get {
                 return ResourceManager.GetString("EightBallQuestionable4", resourceCulture);
             }
         }
-        
+
         internal static string EightBallQuestionable5 {
             get {
                 return ResourceManager.GetString("EightBallQuestionable5", resourceCulture);
             }
         }
-        
+
         internal static string EightBallNeutral1 {
             get {
                 return ResourceManager.GetString("EightBallNeutral1", resourceCulture);
             }
         }
-        
+
         internal static string EightBallNeutral2 {
             get {
                 return ResourceManager.GetString("EightBallNeutral2", resourceCulture);
             }
         }
-        
+
         internal static string EightBallNeutral3 {
             get {
                 return ResourceManager.GetString("EightBallNeutral3", resourceCulture);
             }
         }
-        
+
         internal static string EightBallNeutral4 {
             get {
                 return ResourceManager.GetString("EightBallNeutral4", resourceCulture);
             }
         }
-        
+
         internal static string EightBallNeutral5 {
             get {
                 return ResourceManager.GetString("EightBallNeutral5", resourceCulture);
             }
         }
-        
+
         internal static string EightBallNegative1 {
             get {
                 return ResourceManager.GetString("EightBallNegative1", resourceCulture);
             }
         }
-        
+
         internal static string EightBallNegative2 {
             get {
                 return ResourceManager.GetString("EightBallNegative2", resourceCulture);
             }
         }
-        
+
         internal static string EightBallNegative3 {
             get {
                 return ResourceManager.GetString("EightBallNegative3", resourceCulture);
             }
         }
-        
+
         internal static string EightBallNegative4 {
             get {
                 return ResourceManager.GetString("EightBallNegative4", resourceCulture);
             }
         }
-        
+
         internal static string EightBallNegative5 {
             get {
                 return ResourceManager.GetString("EightBallNegative5", resourceCulture);
             }
         }
-        
+
         internal static string TimeSpanExample {
             get {
                 return ResourceManager.GetString("TimeSpanExample", resourceCulture);
             }
         }
-        
+
         internal static string Version {
             get {
                 return ResourceManager.GetString("Version", resourceCulture);
             }
         }
-        
+
         internal static string SettingsWelcomeMessagesChannel {
             get {
                 return ResourceManager.GetString("SettingsWelcomeMessagesChannel", resourceCulture);
             }
         }
-        
+
+        internal static string UserWarned {
+            get {
+                return ResourceManager.GetString("UserWarned", resourceCulture);
+            }
+        }
+
+        internal static string UserWarnsRemoved {
+            get {
+                return ResourceManager.GetString("UserWarnsRemoved", resourceCulture);
+            }
+        }
+
+        internal static string YouHaveBeenWarned {
+            get {
+                return ResourceManager.GetString("YouHaveBeenWarned", resourceCulture);
+            }
+        }
+
+        internal static string YourWarningsHaveBeenRevoked {
+            get {
+                return ResourceManager.GetString("YourWarningsHaveBeenRevoked", resourceCulture);
+            }
+        }
+
+        internal static string DescriptionWarns {
+            get {
+                return ResourceManager.GetString("DescriptionWarns", resourceCulture);
+            }
+        }
+
+        internal static string UserHasNoWarnings {
+            get {
+                return ResourceManager.GetString("UserHasNoWarnings", resourceCulture);
+            }
+        }
+
+        internal static string YouHaveNoWarnings {
+            get {
+                return ResourceManager.GetString("YouHaveNoWarnings", resourceCulture);
+            }
+        }
+
+        internal static string ReceivedTooManyWarnings {
+            get {
+                return ResourceManager.GetString("ReceivedTooManyWarnings", resourceCulture);
+            }
+        }
         internal static string ButtonDirty {
             get {
                 return ResourceManager.GetString("ButtonDirty", resourceCulture);
             }
         }
-        
+
         internal static string ButtonOpenWiki {
             get {
                 return ResourceManager.GetString("ButtonOpenWiki", resourceCulture);
             }
         }
-        
+
         internal static string SettingsModeratorRole {
             get {
                 return ResourceManager.GetString("SettingsModeratorRole", resourceCulture);
             }
         }
+
+        internal static string ListTargetWarnsTitle {
+            get {
+                return ResourceManager.GetString("ListTargetWarnsTitle", resourceCulture);
+            }
+        }
+
+        internal static string ReceivedOn {
+            get {
+                return ResourceManager.GetString("ReceivedOn", resourceCulture);
+            }
+        }
+
+        internal static string UserWarnRemoved {
+            get {
+                return ResourceManager.GetString("UserWarnRemoved", resourceCulture);
+            }
+        }
+
+        internal static string YourWarningHasBeenRevoked {
+            get {
+                return ResourceManager.GetString("YourWarningHasBeenRevoked", resourceCulture);
+            }
+        }
+
+        internal static string WrongWarningNumberSelected {
+            get {
+                return ResourceManager.GetString("WrongWarningNumberSelected", resourceCulture);
+            }
+        }
+
+        internal static string ListExecutorWarnsTitle {
+            get {
+                return ResourceManager.GetString("ListExecutorWarnsTitle", resourceCulture);
+            }
+        }
+
+        internal static string DescriptionPunishmentType {
+            get {
+                return ResourceManager.GetString("DescriptionPunishmentType", resourceCulture);
+            }
+        }
+
+        internal static string WarnThresholdExceeded {
+            get {
+                return ResourceManager.GetString("WarnThresholdExceeded", resourceCulture);
+            }
+        }
+
+        internal static string WarnPunishmentDurationNotSet {
+            get {
+                return ResourceManager.GetString("WarnPunishmentDurationNotSet", resourceCulture);
+            }
+        }
+
+        internal static string WarnThresholdExceededDescription {
+            get {
+                return ResourceManager.GetString("WarnThresholdExceededDescription", resourceCulture);
+            }
+        }
+
+        internal static string InvalidWarnPunishment {
+            get {
+                return ResourceManager.GetString("InvalidWarnPunishment", resourceCulture);
+            }
+        }
     }
 }
diff --git a/TeamOctolings.Octobot/Messages.resx b/TeamOctolings.Octobot/Messages.resx
index 47e7d4f..037ad3f 100644
--- a/TeamOctolings.Octobot/Messages.resx
+++ b/TeamOctolings.Octobot/Messages.resx
@@ -681,4 +681,124 @@
     <data name="SettingsModeratorRole" xml:space="preserve">
       <value>Moderator role</value>
   </data>
+  <data name="UserWarned" xml:space="preserve">
+      <value>{0} received a warning</value>
+  </data>
+  <data name="UserWarnsRemoved" xml:space="preserve">
+      <value>{0} no longer has warnings</value>
+  </data>
+  <data name="YouHaveBeenWarned" xml:space="preserve">
+      <value>You have been warned</value>
+  </data>
+  <data name="YourWarningsHaveBeenRevoked" xml:space="preserve">
+      <value>Your warnings have been revoked</value>
+  </data>
+  <data name="DescriptionWarns" xml:space="preserve">
+      <value>Warns: {0}</value>
+  </data>
+  <data name="UserHasNoWarnings" xml:space="preserve">
+      <value>This user has no warnings!</value>
+  </data>
+  <data name="ReceivedTooManyWarnings" xml:space="preserve">
+      <value>Received too many warnings</value>
+  </data>
+  <data name="SettingsWarnPunishment" xml:space="preserve">
+      <value>Punishment type for warnings</value>
+  </data>
+  <data name="SettingsWarnThreshold" xml:space="preserve">
+      <value>Warnings threshold</value>
+  </data>
+  <data name="SettingsWarnPunishmentDuration" xml:space="preserve">
+      <value>Punishment duration for warnings</value>
+  </data>
+  <data name="ListExecutorWarnsTitle" xml:space="preserve">
+      <value>Here's your warnings, {0}:</value>
+  </data>
+  <data name="YouHaveNoWarnings" xml:space="preserve">
+      <value>You have no warnings!</value>
+  </data>
+  <data name="ReceivedOn" xml:space="preserve">
+      <value>Received on {0}</value>
+  </data>
+  <data name="UserWarnRemoved" xml:space="preserve">
+      <value>Warning #{0} has been removed from {1}</value>
+  </data>
+  <data name="YourWarningHasBeenRevoked" xml:space="preserve">
+      <value>Your warning has been revoked</value>
+  </data>
+  <data name="WrongWarningNumberSelected" xml:space="preserve">
+      <value>Wrong warning number selected!</value>
+  </data>
+  <data name="UserCannotWarnBot" xml:space="preserve">
+      <value>You cannot warn me!</value>
+  </data>
+  <data name="UserCannotWarnOwner" xml:space="preserve">
+      <value>You cannot warn the owner of this guild!</value>
+  </data>
+  <data name="UserCannotWarnTarget" xml:space="preserve">
+      <value>You cannot warn this member!</value>
+  </data>
+  <data name="UserCannotWarnThemselves" xml:space="preserve">
+      <value>You cannot warn yourself!</value>
+  </data>
+  <data name="UserCannotUnwarnBot" xml:space="preserve">
+      <value>You cannot unwarn me!</value>
+  </data>
+  <data name="UserCannotUnwarnOwner" xml:space="preserve">
+      <value>You cannot unwarn the owner of this guild!</value>
+  </data>
+  <data name="UserCannotUnwarnTarget" xml:space="preserve">
+      <value>You cannot unwarn this member!</value>
+  </data>
+  <data name="UserCannotUnwarnThemselves" xml:space="preserve">
+      <value>You cannot unwarn yourself!</value>
+  </data>
+  <data name="BotCannotWarnMembers" xml:space="preserve">
+      <value>I cannot warn members from this guild!</value>
+  </data>
+  <data name="BotCannotWarnTarget" xml:space="preserve">
+      <value>I cannot warn this member!</value>
+  </data>
+  <data name="BotCannotUnwarnTarget" xml:space="preserve">
+      <value>I cannot unwarn this member!</value>
+  </data>
+  <data name="UserCannotGetWarnsBot" xml:space="preserve">
+      <value>You cannot get my warns!</value>
+  </data>
+  <data name="UserCannotGetWarnsOwner" xml:space="preserve">
+      <value>You cannot get owner's warns!</value>
+  </data>
+  <data name="UserCannotGetWarnsTarget" xml:space="preserve">
+      <value>You cannot get warns of this member!</value>
+  </data>
+  <data name="UserCannotGetWarnsThemselves" xml:space="preserve">
+      <value>Use this command without options instead.</value>
+  </data>
+  <data name="UserCannotWarnMembers" xml:space="preserve">
+      <value>You cannot warn members in this guild!</value>
+  </data>
+  <data name="UserCannotUnwarnMembers" xml:space="preserve">
+      <value>You cannot unwarn members in this guild!</value>
+  </data>
+  <data name="UserCannotGetWarnsMembers" xml:space="preserve">
+      <value>You cannot get warns of other members in this guild!</value>
+  </data>
+  <data name="ListTargetWarnsTitle" xml:space="preserve">
+      <value>Warnings given to {0}:</value>
+  </data>
+  <data name="DescriptionPunishmentType" xml:space="preserve">
+      <value>Punishment type: {0}</value>
+  </data>
+  <data name="WarnThresholdExceeded" xml:space="preserve">
+      <value>Warn threshold has been exceeded. ({0})</value>
+  </data>
+  <data name="WarnPunishmentDurationNotSet" xml:space="preserve">
+      <value>Warn Punishment Duration is not set for the current punishment type.</value>
+  </data>
+  <data name="WarnThresholdExceededDescription" xml:space="preserve">
+      <value>Increase the Warn Threshold or set a Warn Punishment.</value>
+  </data>
+  <data name="InvalidWarnPunishment" xml:space="preserve">
+      <value>Invalid Warn Punishment is set.</value>
+  </data>
 </root>
diff --git a/TeamOctolings.Octobot/Messages.ru.resx b/TeamOctolings.Octobot/Messages.ru.resx
index 2eef257..68062d4 100644
--- a/TeamOctolings.Octobot/Messages.ru.resx
+++ b/TeamOctolings.Octobot/Messages.ru.resx
@@ -681,4 +681,124 @@
     <data name="SettingsModeratorRole" xml:space="preserve">
       <value>Роль модератора</value>
   </data>
+  <data name="UserWarned" xml:space="preserve">
+      <value>{0} получил предупреждение</value>
+  </data>
+  <data name="UserWarnsRemoved" xml:space="preserve">
+      <value>{0} больше не имеет предупреждений</value>
+  </data>
+  <data name="YouHaveBeenWarned" xml:space="preserve">
+      <value>Вы получили предупреждение</value>
+  </data>
+  <data name="YourWarningsHaveBeenRevoked" xml:space="preserve">
+      <value>Ваши предупреждения были отозваны</value>
+  </data>
+  <data name="DescriptionWarns" xml:space="preserve">
+      <value>Предупреждений: {0}</value>
+  </data>
+  <data name="UserHasNoWarnings" xml:space="preserve">
+      <value>Этот пользователь не имеет предупреждений!</value>
+  </data>
+  <data name="ReceivedTooManyWarnings" xml:space="preserve">
+      <value>Получил слишком много предупреждений</value>
+  </data>
+  <data name="SettingsWarnPunishment" xml:space="preserve">
+      <value>Тип наказания для предупреждений</value>
+  </data>
+  <data name="SettingsWarnThreshold" xml:space="preserve">
+      <value>Порог предупреждений</value>
+  </data>
+  <data name="SettingsWarnPunishmentDuration" xml:space="preserve">
+      <value>Длительность наказания для предупреждений</value>
+  </data>
+  <data name="ListExecutorWarnsTitle" xml:space="preserve">
+      <value>Вот ваши предупреждения, {0}:</value>
+  </data>
+  <data name="YouHaveNoWarnings" xml:space="preserve">
+      <value>У вас нет предупреждений!</value>
+  </data>
+  <data name="ReceivedOn" xml:space="preserve">
+      <value>Получено {0}</value>
+  </data>
+  <data name="UserWarnRemoved" xml:space="preserve">
+      <value>Предупреждение №{0} было снято с {1}</value>
+  </data>
+  <data name="YourWarningHasBeenRevoked" xml:space="preserve">
+      <value>Ваше предупреждение было отозвано</value>
+  </data>
+  <data name="WrongWarningNumberSelected" xml:space="preserve">
+      <value>Выбрано неверное число предупреждения!</value>
+  </data>
+  <data name="UserCannotWarnBot" xml:space="preserve">
+      <value>Ты не можешь меня предупредить!</value>
+  </data>
+  <data name="UserCannotWarnOwner" xml:space="preserve">
+      <value>Ты не можешь предупредить владельца этого сервера!</value>
+  </data>
+  <data name="UserCannotWarnTarget" xml:space="preserve">
+      <value>Ты не можешь предупредить этого участника!</value>
+  </data>
+  <data name="UserCannotWarnThemselves" xml:space="preserve">
+      <value>Ты не можешь себя предупредить!</value>
+  </data>
+  <data name="UserCannotUnwarnBot" xml:space="preserve">
+      <value>Ты не можешь снять с меня предупреждения!</value>
+  </data>
+  <data name="UserCannotUnwarnOwner" xml:space="preserve">
+      <value>Ты не можешь снять предупреждения с владельца этого сервера!</value>
+  </data>
+  <data name="UserCannotUnwarnTarget" xml:space="preserve">
+      <value>Ты не можешь снять предупреждения с этого участника!</value>
+  </data>
+  <data name="UserCannotUnwarnThemselves" xml:space="preserve">
+      <value>Ты не можешь снять с себя предупреждения!</value>
+  </data>
+  <data name="BotCannotUnwarnTarget" xml:space="preserve">
+      <value>Я не могу снимать предупреждения этого участника!</value>
+  </data>
+  <data name="BotCannotWarnTarget" xml:space="preserve">
+      <value>Я не могу предупредить этого участника!</value>
+  </data>
+  <data name="BotCannotWarnMembers" xml:space="preserve">
+      <value>Я не могу предупреждать участников этого сервера!</value>
+  </data>
+  <data name="UserCannotGetWarnsBot" xml:space="preserve">
+      <value>Ты не можешь просмотреть мои предупреждения!</value>
+  </data>
+  <data name="UserCannotGetWarnsOwner" xml:space="preserve">
+      <value>Ты не можешь просмотреть предупреждения владельца этого сервера!</value>
+  </data>
+  <data name="UserCannotGetWarnsTarget" xml:space="preserve">
+      <value>Ты не можешь просмотреть предупреждения этого участника!</value>
+  </data>
+  <data name="UserCannotGetWarnsThemselves" xml:space="preserve">
+      <value>Вместо этого, используйте эту команду без параметров.</value>
+  </data>
+  <data name="UserCannotUnwarnMembers" xml:space="preserve">
+      <value>Ты не можешь снимать предупреждения с участников этого сервера!</value>
+  </data>
+  <data name="UserCannotWarnMembers" xml:space="preserve">
+      <value>Ты не можешь предупреждать участников этого сервера!</value>
+  </data>
+  <data name="UserCannotGetWarnsMembers" xml:space="preserve">
+      <value>Ты не можешь просматривать предупреждения участников этого сервера!</value>
+  </data>
+  <data name="ListTargetWarnsTitle" xml:space="preserve">
+      <value>Предупреждения пользователя {0}:</value>
+  </data>
+  <data name="DescriptionPunishmentType" xml:space="preserve">
+      <value>Тип наказания: {0}</value>
+  </data>
+  <data name="WarnThresholdExceeded" xml:space="preserve">
+      <value>Превышен порог предупреждений. ({0})</value>
+  </data>
+  <data name="WarnPunishmentDurationNotSet" xml:space="preserve">
+      <value>Длительность наказания предупреждения не установлена для текущего типа наказания.</value>
+  </data>
+  <data name="WarnThresholdExceededDescription" xml:space="preserve">
+      <value>Увеличьте порог предупреждения или установите наказание за предупреждение.</value>
+  </data>
+  <data name="InvalidWarnPunishment" xml:space="preserve">
+      <value>Установлено неверное наказание за предупреждение.</value>
+  </data>
 </root>
diff --git a/TeamOctolings.Octobot/Services/AccessControlService.cs b/TeamOctolings.Octobot/Services/AccessControlService.cs
index d39c9e5..826983e 100644
--- a/TeamOctolings.Octobot/Services/AccessControlService.cs
+++ b/TeamOctolings.Octobot/Services/AccessControlService.cs
@@ -100,7 +100,8 @@ public sealed class AccessControlService
             {
                 "Ban" => DiscordPermission.BanMembers,
                 "Kick" => DiscordPermission.KickMembers,
-                "Mute" or "Unmute" => DiscordPermission.ModerateMembers,
+                "Mute" or "Unmute" or "Warn" or "Unwarn" or "GetWarns"
+                    => DiscordPermission.ModerateMembers,
                 _ => throw new Exception()
             });