Skip to content

Commit

Permalink
Add tqsunmute command (#274)
Browse files Browse the repository at this point in the history
* Add tqsunmute command

* tqsunmute: Address PR feedback

Allow usage in #bot-commands (adds config.json value), move `else if` into `catch`
  • Loading branch information
FloatingMilkshake authored Mar 1, 2025
1 parent dbea72a commit 2147716
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 18 deletions.
85 changes: 83 additions & 2 deletions Commands/MuteCmds.cs
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,89 @@ public async Task TqsMuteSlashCommand(
if (ctx is SlashCommandContext)
await ctx.EditResponseAsync(new DiscordWebhookBuilder().WithContent("Done. Please open a modmail thread for this user if you haven't already!"));
}

[Command("tqsunmute")]
[TextAlias("tqs-unmute", "untqsmute")]
[Description("Removes a TQS Mute from a previously TQS-muted user. See also: tqsmute")]
[AllowedProcessors(typeof(TextCommandProcessor), typeof(SlashCommandProcessor))]
[HomeServer, RequireHomeserverPerm(ServerPermLevel.TechnicalQueriesSlayer)]
public async Task TqsUnmuteCmd(CommandContext ctx, [Parameter("user"), Description("The user you're trying to unmute.")] DiscordUser targetUser, [Description("The reason for the unmute.")] string reason)
{
if (ctx is SlashCommandContext)
await ctx.As<SlashCommandContext>().DeferResponseAsync();

// only work if TQS mute role is configured
if (Program.cfgjson.TqsMutedRole == 0)
{
if (ctx is SlashCommandContext)
await ctx.EditResponseAsync(new DiscordWebhookBuilder().WithContent($"{Program.cfgjson.Emoji.Error} TQS mutes are not configured, so this command does nothing. Please contact the bot maintainer if this is unexpected."));
else
await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} TQS mutes are not configured, so this command does nothing. Please contact the bot maintainer if this is unexpected.");
return;
}

// Only allow usage in #tech-support, #tech-support-forum, and their threads + #bot-commands
if (ctx.Channel.Id != Program.cfgjson.TechSupportChannel &&
ctx.Channel.Id != Program.cfgjson.SupportForumId &&
ctx.Channel.Parent.Id != Program.cfgjson.TechSupportChannel &&
ctx.Channel.Parent.Id != Program.cfgjson.SupportForumId &&
ctx.Channel.Id != Program.cfgjson.BotCommandsChannel)
{
if (ctx is SlashCommandContext)
await ctx.EditResponseAsync(new DiscordWebhookBuilder().WithContent($"{Program.cfgjson.Emoji.Error} This command can only be used in <#{Program.cfgjson.TechSupportChannel}>, <#{Program.cfgjson.SupportForumId}>, and threads in those channels!"));
else
await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} This command can only be used in <#{Program.cfgjson.TechSupportChannel}>, <#{Program.cfgjson.SupportForumId}>, their threads, and <#{Program.cfgjson.BotCommandsChannel}>!");
return;
}

// Get muted roles
DiscordRole mutedRole = await ctx.Guild.GetRoleAsync(Program.cfgjson.MutedRole);
DiscordRole tqsMutedRole = await ctx.Guild.GetRoleAsync(Program.cfgjson.TqsMutedRole);

// Get member
DiscordMember targetMember = default;
try
{
targetMember = await ctx.Guild.GetMemberAsync(targetUser.Id);
}
catch (DSharpPlus.Exceptions.NotFoundException)
{
// couldn't fetch member, fail
if (ctx is SlashCommandContext)
await ctx.EditResponseAsync($"{Program.cfgjson.Emoji.Error} That user doesn't appear to be in the server!");
else
await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} That user doesn't appear to be in the server!");
return;
}

if (await Program.db.HashExistsAsync("mutes", targetUser.Id) && targetMember is not null && targetMember.Roles.Contains(tqsMutedRole))
{
// If the member has a regular mute, leave the TQS mute alone (it's only a role now & it has no effect if they also have Muted); it will be removed when they are unmuted
if (targetMember.Roles.Contains(mutedRole))
{
if (ctx is SlashCommandContext)
await ctx.EditResponseAsync($"{Program.cfgjson.Emoji.Error} {targetUser.Mention} has been muted by a Moderator! Their TQS Mute will be removed when the Moderator-issued mute expires.");
else
await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} {targetUser.Mention} has been muted by a Moderator! Their TQS Mute will be removed when the Moderator-issued mute expires.");
return;
}

// user is TQS-muted; unmute
await MuteHelpers.UnmuteUserAsync(targetUser, reason, true, ctx.User, true);
if (ctx is SlashCommandContext)
await ctx.EditResponseAsync($"{Program.cfgjson.Emoji.Success} Successfully unmuted {targetUser.Mention}!");
else
await ctx.RespondAsync($"{Program.cfgjson.Emoji.Success} Successfully unmuted {targetUser.Mention}!");
}
else
{
// member is not TQS-muted, fail
if (ctx is SlashCommandContext)
await ctx.EditResponseAsync($"{Program.cfgjson.Emoji.Error} That user doesn't appear to be TQS-muted!");
else
await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} That user doesn't appear to be TQS-muted!");
}
}

[Command("muteinfo")]
[Description("Show information about the mute for a user.")]
Expand All @@ -223,8 +306,6 @@ public async Task MuteInfoSlashCommand(
[HomeServer, RequireHomeserverPerm(ServerPermLevel.TrialModerator)]
public async Task UnmuteCmd(TextCommandContext ctx, [Description("The user you're trying to unmute.")] DiscordUser targetUser, string reason = "No reason provided.")
{
reason = $"[Manual unmute by {DiscordHelpers.UniqueUsername(ctx.User)}]: {reason}";

// todo: store per-guild
DiscordRole mutedRole = await ctx.Guild.GetRoleAsync(Program.cfgjson.MutedRole);
DiscordRole tqsMutedRole = default;
Expand Down
38 changes: 23 additions & 15 deletions Helpers/MuteHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -319,8 +319,12 @@ public static (int MuteHours, int WarnsSinceThreshold) GetHoursToMuteFor(Diction
return output;
}

public static async Task<bool> UnmuteUserAsync(DiscordUser targetUser, string reason = "", bool manual = true, DiscordUser modUser = default)
public static async Task<bool> UnmuteUserAsync(DiscordUser targetUser, string reason = "", bool manual = true, DiscordUser modUser = default, bool isTqsUnmute = false)
{
var auditLogReason = reason;
if (manual && modUser is not null)
auditLogReason = $"[Manual {(isTqsUnmute ? "TQS " : "")}unmute by {DiscordHelpers.UniqueUsername(modUser)}]: {reason}";

var muteDetailsJson = await Program.db.HashGetAsync("mutes", targetUser.Id);
bool success = false;
bool wasTqsMute = false;
Expand All @@ -346,7 +350,7 @@ public static async Task<bool> UnmuteUserAsync(DiscordUser targetUser, string re
{
await LogChannelHelper.LogMessageAsync("mod",
new DiscordMessageBuilder()
.WithContent($"{Program.cfgjson.Emoji.Information} Attempt to remove Muted role from {targetUser.Mention} failed because the user could not be found.\nThis is expected if the user was banned or left.")
.WithContent($"{Program.cfgjson.Emoji.Information} Attempt to remove {(isTqsUnmute ? "TQS " : "")}Muted role from {targetUser.Mention} failed because the user could not be found.\nThis is expected if the user was banned or left.")
.WithAllowedMentions(Mentions.None)
);
}
Expand All @@ -361,29 +365,33 @@ await LogChannelHelper.LogMessageAsync("mod",
// If both attempts fail, do standard failure error handling.
try
{
await member.RevokeRoleAsync(role: mutedRole, reason);
await member.RevokeRoleAsync(role: mutedRole, auditLogReason);
}
finally
{
// Check member roles for TQS mute role
if (member.Roles.Contains(tqsMutedRole))
{
await member.RevokeRoleAsync(role: tqsMutedRole, reason);
await member.RevokeRoleAsync(role: tqsMutedRole, auditLogReason);
wasTqsMute = true; // only true if TQS mute role was found & removed
}
}

foreach (var role in member.Roles)
// Skip if not TQS unmute...
if (!isTqsUnmute)
{
if (role.Name == "Muted" && role.Id != Program.cfgjson.MutedRole)
foreach (var role in member.Roles)
{
try
{
await member.RevokeRoleAsync(role: role, reason: reason);
}
catch
if (role.Name == "Muted" && role.Id != Program.cfgjson.MutedRole)
{
// ignore, continue to next role
try
{
await member.RevokeRoleAsync(role: role, reason: auditLogReason);
}
catch
{
// ignore, continue to next role
}
}
}
}
Expand All @@ -393,7 +401,7 @@ await LogChannelHelper.LogMessageAsync("mod",
{
await LogChannelHelper.LogMessageAsync("mod",
new DiscordMessageBuilder()
.WithContent($"{Program.cfgjson.Emoji.Error} Attempt to removed Muted role from {targetUser.Mention} failed because of a Discord API error!" +
.WithContent($"{Program.cfgjson.Emoji.Error} Attempt to remove {(isTqsUnmute ? "TQS " : "")}Muted role from {targetUser.Mention} failed because of a Discord API error!" +
$"\nIf the role was removed manually, this error can be disregarded safely.")
.WithAllowedMentions(Mentions.None)
);
Expand All @@ -404,7 +412,7 @@ await LogChannelHelper.LogMessageAsync("mod",
// TQS mutes are not server-wide so this would fail every time for TQS mutes,
// and we don't want to log a failure for every removed TQS mute
if (!wasTqsMute)
await member.TimeoutAsync(until: null, reason: reason);
await member.TimeoutAsync(until: null, reason: auditLogReason);
}
catch (Exception ex)
{
Expand All @@ -414,7 +422,7 @@ await LogChannelHelper.LogMessageAsync("mod",
if (success)
{
string unmuteMsg = manual
? $"{Program.cfgjson.Emoji.Information} {targetUser.Mention} was successfully unmuted by {modUser.Mention}!"
? $"{Program.cfgjson.Emoji.Information} {targetUser.Mention} was successfully {(isTqsUnmute ? "TQS-" : "")}unmuted by {modUser.Mention}!\nReason: **{reason}**"
: $"{Program.cfgjson.Emoji.Information} Successfully unmuted {targetUser.Mention}!";

await LogChannelHelper.LogMessageAsync("mod", new DiscordMessageBuilder().WithContent(unmuteMsg).WithAllowedMentions(Mentions.None));
Expand Down
3 changes: 3 additions & 0 deletions Structs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,9 @@ public ulong InsidersChannel

[JsonProperty("githubWorkflowSucessString")]
public string GithubWorkflowSucessString { get; private set; } = "";

[JsonProperty("botCommandsChannel")]
public ulong BotCommandsChannel { get; private set; }
}

public enum Level { Information, Warning, Error, Debug, Verbose }
Expand Down
3 changes: 2 additions & 1 deletion config.json
Original file line number Diff line number Diff line change
Expand Up @@ -348,5 +348,6 @@
450181490345508884
],
"pingBotOwnersOnBadErrors": true,
"githubWorkflowSucessString": "[lists:main] 1 new commit"
"githubWorkflowSucessString": "[lists:main] 1 new commit",
"botCommandsChannel": 740272437719072808
}

0 comments on commit 2147716

Please sign in to comment.