Add timeout setting to CF
This commit is contained in:
parent
d5728ad0a1
commit
c4c13b733f
4 changed files with 168 additions and 0 deletions
18
Services/CommonFunctions/CF_Timeout.Hooks.cs
Normal file
18
Services/CommonFunctions/CF_Timeout.Hooks.cs
Normal 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);
|
||||||
|
}
|
65
Services/CommonFunctions/CF_Timeout.cs
Normal file
65
Services/CommonFunctions/CF_Timeout.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
40
Services/CommonFunctions/IOperationResult.cs
Normal file
40
Services/CommonFunctions/IOperationResult.cs
Normal 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();
|
||||||
|
}
|
45
Services/CommonFunctions/TimeoutSetResult.cs
Normal file
45
Services/CommonFunctions/TimeoutSetResult.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue