diff --git a/Commands/InteractionCommands/SecurityActionInteractions.cs b/Commands/InteractionCommands/SecurityActionInteractions.cs new file mode 100644 index 00000000..e9d60605 --- /dev/null +++ b/Commands/InteractionCommands/SecurityActionInteractions.cs @@ -0,0 +1,100 @@ +namespace Cliptok.Commands.InteractionCommands +{ + public class SecurityActionInteractions : ApplicationCommandModule + { + [SlashCommand("pausedms", "Temporarily pause DMs between server members.", defaultPermission: false)] + [HomeServer, SlashRequireHomeserverPerm(ServerPermLevel.Moderator), SlashCommandPermissions(Permissions.ModerateMembers)] + public async Task SlashPauseDMs(InteractionContext ctx, [Option("time", "The amount of time to pause DMs for. Cannot be greater than 24 hours.")] string time) + { + // need to make our own api calls because D#+ can't do this natively? + + // parse time from message + DateTime t = HumanDateParser.HumanDateParser.Parse(time); + if (t <= DateTime.Now) + { + await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} Time can't be in the past!"); + return; + } + if (t > DateTime.Now.AddHours(24)) + { + await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} Time can't be greater than 24 hours!"); + return; + } + var dmsDisabledUntil = t.ToUniversalTime().ToString("o"); + + // get current security actions to avoid unintentionally resetting invites_disabled_until + var currentActions = await SecurityActionHelpers.GetCurrentSecurityActions(ctx.Guild.Id); + JToken invitesDisabledUntil; + if (currentActions is null || !currentActions.HasValues) + invitesDisabledUntil = null; + else + invitesDisabledUntil = currentActions["invites_disabled_until"]; + + // create json body + var newSecurityActions = JsonConvert.SerializeObject(new + { + invites_disabled_until = invitesDisabledUntil, + dms_disabled_until = dmsDisabledUntil, + }); + + // set actions + var setActionsResponse = await SecurityActionHelpers.SetCurrentSecurityActions(ctx.Guild.Id, newSecurityActions); + + // respond + if (setActionsResponse.IsSuccessStatusCode) + await ctx.RespondAsync($"{Program.cfgjson.Emoji.Success} Successfully paused DMs until !"); + else + { + ctx.Client.Logger.LogError("Failed to set Security Actions.\nPayload: {payload}\nResponse: {statuscode} {body}", newSecurityActions.ToString(), (int)setActionsResponse.StatusCode, await setActionsResponse.Content.ReadAsStringAsync()); + await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} Something went wrong and I wasn't able to unpause DMs! Discord returned status code `{setActionsResponse.StatusCode}`."); + } + } + + [SlashCommand("unpausedms", "Unpause DMs between server members.", defaultPermission: false)] + [HomeServer, SlashRequireHomeserverPerm(ServerPermLevel.Moderator), SlashCommandPermissions(Permissions.ModerateMembers)] + public async Task SlashUnpauseDMs(InteractionContext ctx) + { + // need to make our own api calls because D#+ can't do this natively? + + // get current security actions to avoid unintentionally resetting invites_disabled_until + var currentActions = await SecurityActionHelpers.GetCurrentSecurityActions(ctx.Guild.Id); + JToken dmsDisabledUntil, invitesDisabledUntil; + if (currentActions is null || !currentActions.HasValues) + { + dmsDisabledUntil = null; + invitesDisabledUntil = null; + } + else + { + dmsDisabledUntil = currentActions["dms_disabled_until"]; + invitesDisabledUntil = currentActions["invites_disabled_until"]; + } + + // if dms are already unpaused, return error + if (dmsDisabledUntil is null) + { + await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} DMs are already unpaused!"); + return; + } + + // create json body + var newSecurityActions = JsonConvert.SerializeObject(new + { + invites_disabled_until = invitesDisabledUntil, + dms_disabled_until = (object)null, + }); + + // set actions + var setActionsResponse = await SecurityActionHelpers.SetCurrentSecurityActions(ctx.Guild.Id, newSecurityActions); + + // respond + if (setActionsResponse.IsSuccessStatusCode) + await ctx.RespondAsync($"{Program.cfgjson.Emoji.Success} Successfully unpaused DMs!"); + else + { + ctx.Client.Logger.LogError("Failed to set Security Actions.\nPayload: {payload}\nResponse: {statuscode} {body}", newSecurityActions.ToString(), (int)setActionsResponse.StatusCode, await setActionsResponse.Content.ReadAsStringAsync()); + await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} Something went wrong and I wasn't able to pause DMs! Discord returned status code `{setActionsResponse.StatusCode}`."); + } + } + } +} \ No newline at end of file diff --git a/Commands/SecurityActions.cs b/Commands/SecurityActions.cs new file mode 100644 index 00000000..98cdf9ef --- /dev/null +++ b/Commands/SecurityActions.cs @@ -0,0 +1,117 @@ +namespace Cliptok.Commands +{ + public class SecurityActions : BaseCommandModule + { + [Command("pausedms")] + [Description("Temporarily pause DMs between server members.")] + [HomeServer, RequireHomeserverPerm(ServerPermLevel.Moderator)] + public async Task PauseDMs(CommandContext ctx, [Description("The amount of time to pause DMs for."), RemainingText] string time) + { + if (string.IsNullOrWhiteSpace(time)) + { + await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} You must provide an amount of time to pause DMs for!"); + return; + } + + // need to make our own api calls because D#+ can't do this natively? + + // parse time from message + DateTime t; + try + { + t = HumanDateParser.HumanDateParser.Parse(time); + } + catch (HumanDateParser.ParseException) + { + await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} I didn't understand that time! Please try again."); + return; + } + if (t <= DateTime.Now) + { + await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} Time can't be in the past!"); + return; + } + if (t > DateTime.Now.AddHours(24)) + { + await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} Time can't be greater than 24 hours!"); + return; + } + var dmsDisabledUntil = t.ToUniversalTime().ToString("o"); + + // get current security actions to avoid unintentionally resetting invites_disabled_until + var currentActions = await SecurityActionHelpers.GetCurrentSecurityActions(ctx.Guild.Id); + JToken invitesDisabledUntil; + if (currentActions is null || !currentActions.HasValues) + invitesDisabledUntil = null; + else + invitesDisabledUntil = currentActions["invites_disabled_until"]; + + // create json body + var newSecurityActions = JsonConvert.SerializeObject(new + { + invites_disabled_until = invitesDisabledUntil, + dms_disabled_until = dmsDisabledUntil, + }); + + // set actions + var setActionsResponse = await SecurityActionHelpers.SetCurrentSecurityActions(ctx.Guild.Id, newSecurityActions); + + // respond + if (setActionsResponse.IsSuccessStatusCode) + await ctx.RespondAsync($"{Program.cfgjson.Emoji.Success} Successfully paused DMs for **{TimeHelpers.TimeToPrettyFormat(t.Subtract(ctx.Message.Timestamp.DateTime), false)}**!"); + else + { + ctx.Client.Logger.LogError("Failed to set Security Actions.\nPayload: {payload}\nResponse: {statuscode} {body}", newSecurityActions.ToString(), (int)setActionsResponse.StatusCode, await setActionsResponse.Content.ReadAsStringAsync()); + await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} Something went wrong and I wasn't able to pause DMs! Discord returned status code `{setActionsResponse.StatusCode}`."); + } + } + + [Command("unpausedms")] + [Description("Unpause DMs between server members.")] + [HomeServer, RequireHomeserverPerm(ServerPermLevel.Moderator)] + public async Task UnpauseDMs(CommandContext ctx) + { + // need to make our own api calls because D#+ can't do this natively? + + // get current security actions to avoid unintentionally resetting invites_disabled_until + var currentActions = await SecurityActionHelpers.GetCurrentSecurityActions(ctx.Guild.Id); + JToken dmsDisabledUntil, invitesDisabledUntil; + if (currentActions is null || !currentActions.HasValues) + { + dmsDisabledUntil = null; + invitesDisabledUntil = null; + } + else + { + dmsDisabledUntil = currentActions["dms_disabled_until"]; + invitesDisabledUntil = currentActions["invites_disabled_until"]; + } + + // if dms are already unpaused, return error + if (dmsDisabledUntil is null) + { + await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} DMs are already unpaused!"); + return; + } + + // create json body + var newSecurityActions = JsonConvert.SerializeObject(new + { + invites_disabled_until = invitesDisabledUntil, + dms_disabled_until = (object)null, + }); + + // set actions + var setActionsResponse = await SecurityActionHelpers.SetCurrentSecurityActions(ctx.Guild.Id, newSecurityActions); + + // respond + if (setActionsResponse.IsSuccessStatusCode) + await ctx.RespondAsync($"{Program.cfgjson.Emoji.Success} Successfully unpaused DMs!"); + else + { + ctx.Client.Logger.LogError("Failed to set Security Actions.\nPayload: {payload}\nResponse: {statuscode} {body}", newSecurityActions.ToString(), (int)setActionsResponse.StatusCode, await setActionsResponse.Content.ReadAsStringAsync()); + await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} Something went wrong and I wasn't able to unpause DMs! Discord returned status code `{setActionsResponse.StatusCode}`."); + } + } + } +} \ No newline at end of file diff --git a/GlobalUsings.cs b/GlobalUsings.cs index 6d870565..d8a9ff6c 100644 --- a/GlobalUsings.cs +++ b/GlobalUsings.cs @@ -10,6 +10,7 @@ global using DSharpPlus.SlashCommands; global using Microsoft.Extensions.Logging; global using Newtonsoft.Json; +global using Newtonsoft.Json.Linq; global using Serilog; global using Serilog.Sinks.SystemConsole.Themes; global using StackExchange.Redis; diff --git a/Helpers/SecurityActionHelpers.cs b/Helpers/SecurityActionHelpers.cs new file mode 100644 index 00000000..7734e379 --- /dev/null +++ b/Helpers/SecurityActionHelpers.cs @@ -0,0 +1,25 @@ +namespace Cliptok.Helpers +{ + public class SecurityActionHelpers + { + public static async Task GetCurrentSecurityActions(ulong guildId) + { + using HttpRequestMessage getActionsRequest = new(HttpMethod.Get, $"https://discord.com/api/v{Program.discord.GatewayVersion}/guilds/{guildId}"); + getActionsRequest.Headers.Authorization = new AuthenticationHeaderValue("Bot", Environment.GetEnvironmentVariable("CLIPTOK_TOKEN") ?? Program.cfgjson.Core.Token); + + var getActionsResponse = await Program.httpClient.SendAsync(getActionsRequest); + return ((JObject)JsonConvert.DeserializeObject(await getActionsResponse.Content.ReadAsStringAsync()))["incidents_data"]; + } + + public static async Task SetCurrentSecurityActions(ulong guildId, string newSecurityActions) + { + // create & send request + + using HttpRequestMessage setActionsRequest = new(HttpMethod.Put, $"https://discord.com/api/v{Program.discord.GatewayVersion}/guilds/{guildId}/incident-actions"); + setActionsRequest.Headers.Authorization = new AuthenticationHeaderValue("Bot", Environment.GetEnvironmentVariable("CLIPTOK_TOKEN") ?? Program.cfgjson.Core.Token); + + setActionsRequest.Content = new StringContent(newSecurityActions, Encoding.UTF8, "application/json"); + return await Program.httpClient.SendAsync(setActionsRequest); + } + } +} \ No newline at end of file