Add timeout setting to CF

This commit is contained in:
Noi 2022-09-16 20:42:55 -07:00
parent d5728ad0a1
commit c4c13b733f
4 changed files with 168 additions and 0 deletions

View file

@ -0,0 +1,18 @@
namespace RegexBot;
partial class RegexbotClient {
/// <summary>
/// Sets a timeout on a user while also adding an entry to the moderation log and attempting to notify the user.
/// </summary>
/// <param name="guild">The guild which the target user is associated.</param>
/// <param name="source">
/// The the entity which issued this log item.
/// If it was a user, this value preferably is in the <seealso cref="Common.EntityName"/> format.
/// </param>
/// <param name="target">The user to be issued a timeout.</param>
/// <param name="duration">The duration of the timeout.</param>
/// <param name="reason">Reason for the action. Sent to the Audit Log and user (if specified).</param>
/// <param name="sendNotificationDM">Specify whether to send a direct message to the target user informing them of the action.</param>
public Task<TimeoutSetResult> SetTimeoutAsync(SocketGuild guild, string source, SocketGuildUser target,
TimeSpan duration, string? reason, bool sendNotificationDM)
=> _svcCommonFunctions.SetTimeoutAsync(guild, source, target, duration, reason, sendNotificationDM);
}

View file

@ -0,0 +1,65 @@
#pragma warning disable CA1822 // "Mark members as static" - members should only be callable by code with access to this instance
using Discord.Net;
using RegexBot.Data;
namespace RegexBot.Services.CommonFunctions;
internal partial class CommonFunctionsService : Service {
// Hooked
internal async Task<TimeoutSetResult> SetTimeoutAsync(SocketGuild guild, string source, SocketGuildUser target,
TimeSpan duration, string? reason, bool sendNotificationDM) {
if (duration < TimeSpan.FromMinutes(1))
return new TimeoutSetResult(new ArgumentOutOfRangeException(
nameof(duration), "Cannot set a timeout with a duration less than 60 seconds."), true, target);
if (duration > TimeSpan.FromDays(28))
return new TimeoutSetResult(new ArgumentOutOfRangeException(
nameof(duration), "Cannot set a timeout with a duration greater than 28 days."), true, target);
if (target.TimedOutUntil != null && DateTimeOffset.UtcNow < target.TimedOutUntil)
return new TimeoutSetResult(new InvalidOperationException(
"Cannot set a timeout. The user is already timed out."), true, target);
Discord.RequestOptions? audit = null;
if (reason != null) audit = new() { AuditLogReason = reason };
try {
await target.SetTimeOutAsync(duration, audit);
} catch (HttpException ex) {
return new TimeoutSetResult(ex, false, target);
}
var entry = new ModLogEntry() {
GuildId = (long)guild.Id,
UserId = (long)target.Id,
LogType = ModLogType.Timeout,
IssuedBy = source,
Message = reason
};
using (var db = new BotDatabaseContext()) {
db.Add(entry);
await db.SaveChangesAsync();
}
// TODO check if this log entry should be propagated now or if (to be implemented) will do it for us later
bool dmSuccess;
// DM notification
if (sendNotificationDM) {
dmSuccess = await SendUserTimeoutNoticeAsync(target, duration, reason);
} else dmSuccess = true;
return new TimeoutSetResult(null, dmSuccess, target);
}
internal async Task<bool> SendUserTimeoutNoticeAsync(SocketGuildUser target, TimeSpan duration, string? reason) {
// you have been issued a timeout in x.
// the timeout will expire on <t:...>
const string DMTemplate1 = "You have been issued a timeout in {0}";
const string DMTemplateReason = " for the following reason:\n{2}";
const string DMTemplate2 = "\nThe timeout will expire on <t:{1}:f> (<t:{1}:R>).";
var expireTime = (DateTimeOffset.UtcNow + duration).ToUnixTimeSeconds();
var outMessage = string.IsNullOrWhiteSpace(reason)
? string.Format($"{DMTemplate1}.{DMTemplate2}", target.Guild.Name, expireTime)
: string.Format($"{DMTemplate1}{DMTemplateReason}\n{DMTemplate2}", target.Guild.Name, expireTime, reason);
var dch = await target.CreateDMChannelAsync();
try { await dch.SendMessageAsync(outMessage); } catch (HttpException) { return false; }
return true;
}
}

View file

@ -0,0 +1,40 @@
namespace RegexBot;
/// <summary>
/// Contains information on success or failure outcomes for certain operations.
/// </summary>
public interface IOperationResult {
/// <summary>
/// Indicates whether the operation was successful.
/// </summary>
/// <remarks>
/// Be aware this value may return <see langword="true"/> while
/// <see cref="NotificationSuccess"/> returns <see langword="false"/>.
/// </remarks>
bool Success { get; }
/// <summary>
/// The exception thrown, if any, when attempting to perform the operation.
/// </summary>
Exception? Error { get; }
/// <summary>
/// Indicates if the operation failed due to being unable to find the user.
/// </summary>
bool ErrorNotFound { get; }
/// <summary>
/// Indicates if the operation failed due to a permissions issue.
/// </summary>
bool ErrorForbidden { get; }
/// <summary>
/// Indicates if user DM notification for this event was successful.
/// Always returns <see langword="true"/> in cases where no notification was requested.
/// </summary>
bool NotificationSuccess { get; }
/// <summary>
/// Returns a message representative of this result that may be posted as-is within a Discord channel.
/// </summary>
string ToResultString();
}

View file

@ -0,0 +1,45 @@
using Discord.Net;
using RegexBot.Common;
namespace RegexBot;
/// <summary>
/// Contains information on various success/failure outcomes for setting a timeout.
/// </summary>
public class TimeoutSetResult : IOperationResult {
private readonly SocketGuildUser? _target;
/// <inheritdoc/>
public bool Success => Error == null;
/// <inheritdoc/>
public Exception? Error { get; }
/// <inheritdoc/>
public bool ErrorNotFound => (_target == null) || ((Error as HttpException)?.HttpCode == System.Net.HttpStatusCode.NotFound);
/// <inheritdoc/>
public bool ErrorForbidden => (Error as HttpException)?.HttpCode == System.Net.HttpStatusCode.Forbidden;
/// <inheritdoc/>
public bool NotificationSuccess { get; }
internal TimeoutSetResult(Exception? error, bool notificationSuccess, SocketGuildUser? target) {
Error = error;
NotificationSuccess = notificationSuccess;
_target = target;
}
/// <inheritdoc/>
public string ToResultString() {
if (Success) {
var msg = $":white_check_mark: Timeout set for **{_target!.Username}#{_target.Discriminator}**.";
if (!NotificationSuccess) msg += "\n(User was unable to receive notification message.)";
return msg;
} else {
var msg = ":x: Failed to set timeout: ";
if (ErrorNotFound) msg += ": The specified user could not be found.";
else if (ErrorForbidden) msg += ": " + Messages.ForbiddenGenericError;
return msg;
}
}
}