Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add #insiders-info interactions #255

Merged
merged 7 commits into from
Jan 3, 2025
12 changes: 10 additions & 2 deletions CommandChecks/HomeServerPerms.cs
Original file line number Diff line number Diff line change
Expand Up @@ -137,14 +137,22 @@ public override async Task<bool> ExecuteCheckAsync(CommandContext ctx, bool help
public class SlashRequireHomeserverPermAttribute : SlashCheckBaseAttribute
{
public ServerPermLevel TargetLvl;
public bool OwnerOverride;

public SlashRequireHomeserverPermAttribute(ServerPermLevel targetlvl)
=> TargetLvl = targetlvl;
public SlashRequireHomeserverPermAttribute(ServerPermLevel targetlvl, bool ownerOverride = false)
{
TargetLvl = targetlvl;
OwnerOverride = ownerOverride;
}

public override async Task<bool> ExecuteChecksAsync(InteractionContext ctx)
{
if (ctx.Guild.Id != Program.cfgjson.ServerID)
return false;

// bot owners can bypass perm checks ONLY if the command allows it.
if (OwnerOverride && Program.cfgjson.BotOwners.Contains(ctx.User.Id))
return true;

var level = await GetPermLevelAsync(ctx.Member);
if (level >= TargetLvl)
Expand Down
4 changes: 2 additions & 2 deletions Commands/InteractionCommands/AnnouncementInteractions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ public async Task AnnounceBuildSlashCommand(InteractionContext ctx,
[Option("lockdown", "Set 0 to not lock. Lock the channel for a certain period of time after announcing the build.")] string lockdownTime = "auto"
)
{
if (Program.cfgjson.InsiderCommandLockedToChannel != 0 && ctx.Channel.Id != Program.cfgjson.InsiderCommandLockedToChannel)
if (Program.cfgjson.InsidersChannel != 0 && ctx.Channel.Id != Program.cfgjson.InsidersChannel)
{
await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} This command only works in <#{Program.cfgjson.InsiderCommandLockedToChannel}>!", ephemeral: true);
await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} This command only works in <#{Program.cfgjson.InsidersChannel}>!", ephemeral: true);
return;
}

Expand Down
39 changes: 39 additions & 0 deletions Commands/InteractionCommands/InsidersInteractions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
namespace Cliptok.Commands.InteractionCommands
{
public class InsidersInteractions : ApplicationCommandModule
{
[SlashCommand("send-insiders-info-buttons", "Sends a message with buttons to get Insider roles for #insiders-info.", false)]
[SlashRequireHomeserverPerm(ServerPermLevel.Admin, ownerOverride: true), SlashCommandPermissions(permissions: DiscordPermission.ModerateMembers)]
public static async Task SendInsidersInfoButtonMessage(InteractionContext ctx)
{
if (Program.cfgjson.InsiderInfoChannel != 0 && ctx.Channel.Id != Program.cfgjson.InsiderInfoChannel)
Erisa marked this conversation as resolved.
Show resolved Hide resolved
{
await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} This command only works in <#{Program.cfgjson.InsiderInfoChannel}>!", ephemeral: true);
return;
}

DiscordComponent[] buttons =
[
new DiscordButtonComponent(DiscordButtonStyle.Primary, "insiders-info-roles-menu-callback", "Choose your Insider roles"),
new DiscordButtonComponent(DiscordButtonStyle.Secondary, "insiders-info-chat-btn-callback", "I just want to chat for now")
];

string insidersChannelMention;
if (Program.cfgjson.InsidersChannel == 0)
{
insidersChannelMention = "#insiders";
Program.discord.Logger.LogWarning("#insiders-info message sent with hardcoded #insiders mention! Is insidersChannel set in config.json?");
}
else
{
insidersChannelMention = $"<#{Program.cfgjson.InsidersChannel}>";
}

var builder = new DiscordInteractionResponseBuilder()
.WithContent($"{Program.cfgjson.Emoji.Insider} Choose your Insider roles here! Or, you can choose to chat in {insidersChannelMention} without being notified about new builds.")
.AddComponents(buttons);

await ctx.CreateResponseAsync(builder);
}
}
}
176 changes: 176 additions & 0 deletions Events/InteractionEvents.cs
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,182 @@ await LogChannelHelper.LogDeletedMessagesAsync(
// Respond
await e.Message.ModifyAsync(new DiscordMessageBuilder().WithContent($"{cfgjson.Emoji.Success} Override successfully added. <@{newOverwrite.Id}> already had an override in <#{pendingOverride.ChannelId}>, so here are their new permissions:\n**Allowed:** {newOverwrite.Allowed}\n**Denied:** {newOverwrite.Denied}"));
}
else if (e.Id == "insiders-info-roles-menu-callback")
{
// Shows a menu in #insider-info that allows a user to toggle their Insider roles

// Defer interaction
await e.Interaction.DeferAsync(ephemeral: true);

// Fetch member
var member = await e.Guild.GetMemberAsync(e.User.Id);

// Fetch Insider roles to check whether member already has them
var insiderCanaryRole = await e.Guild.GetRoleAsync(cfgjson.UserRoles.InsiderCanary);
var insiderDevRole = await e.Guild.GetRoleAsync(cfgjson.UserRoles.InsiderDev);
var insiderBetaRole = await e.Guild.GetRoleAsync(cfgjson.UserRoles.InsiderBeta);
var insiderRPRole = await e.Guild.GetRoleAsync(cfgjson.UserRoles.InsiderRP);
var insider10RPRole = await e.Guild.GetRoleAsync(cfgjson.UserRoles.Insider10RP);
var patchTuesdayRole = await e.Guild.GetRoleAsync(cfgjson.UserRoles.PatchTuesday);

// Show menu with current Insider roles, apply new roles based on user selection
var menu = new DiscordSelectComponent("insiders-info-roles-menu-response-callback", "Choose your Insider roles",
new List<DiscordSelectComponentOption>()
{
new("Windows 11 Canary channel", "insiders-info-w11-canary", isDefault: member.Roles.Contains(insiderCanaryRole)),
new("Windows 11 Dev channel", "insiders-info-w11-dev", isDefault: member.Roles.Contains(insiderDevRole)),
new("Windows 11 Beta channel", "insiders-info-w11-beta", isDefault: member.Roles.Contains(insiderBetaRole)),
new("Windows 11 Release Preview channel", "insiders-info-w11-rp", isDefault: member.Roles.Contains(insiderRPRole)),
new("Windows 10 Release Preview channel", "insiders-info-w10-rp", isDefault: member.Roles.Contains(insider10RPRole)),
new("Patch Tuesday", "insiders-info-pt", isDefault: member.Roles.Contains(patchTuesdayRole)),
}, minOptions: 0, maxOptions: 6);

var builder = new DiscordFollowupMessageBuilder()
.WithContent($"{cfgjson.Emoji.Insider} Use the menu below to toggle your Insider roles!")
.AddComponents(menu)
.AsEphemeral(true);

await e.Interaction.CreateFollowupMessageAsync(builder);
}
else if (e.Id == "insiders-info-roles-menu-response-callback")
{
// User has selected new Insider roles w/ menu above
// Compare selection against current roles; add or remove roles as necessary to match selection

// Defer
await e.Interaction.DeferAsync(ephemeral: true);

// Get member
var member = await e.Guild.GetMemberAsync(e.User.Id);

// Map role select options to role IDs
var insiderRoles = new Dictionary<string, ulong>
{
{ "insiders-info-w11-canary", cfgjson.UserRoles.InsiderCanary },
{ "insiders-info-w11-dev", cfgjson.UserRoles.InsiderDev },
{ "insiders-info-w11-beta", cfgjson.UserRoles.InsiderBeta },
{ "insiders-info-w11-rp", cfgjson.UserRoles.InsiderRP },
{ "insiders-info-w10-rp", cfgjson.UserRoles.Insider10RP },
{ "insiders-info-pt", cfgjson.UserRoles.PatchTuesday }
};

// Get a list of the member's current roles that we can add to or remove from
// Then we can apply this in a single request with member.ReplaceRolesAsync to avoid making repeated member update requests
List<DiscordRole> memberRoles = member.Roles.ToList();

var selection = e.Values.Select(x => insiderRoles[x]).ToList();

foreach (var roleId in insiderRoles.Values)
{
var role = await e.Guild.GetRoleAsync(roleId);

if (selection.Contains(roleId))
{
// Member should have the role
if (!memberRoles.Contains(role))
memberRoles.Add(role);
}
else
{
// Member should not have the role
if (memberRoles.Contains(role))
memberRoles.Remove(role);
}
}

await member.ReplaceRolesAsync(memberRoles, "Applying Insider roles chosen in #insiders-info");

await e.Interaction.CreateFollowupMessageAsync(new DiscordFollowupMessageBuilder().WithContent($"{cfgjson.Emoji.Success} Your Insider roles have been updated!").AsEphemeral(true));
}
else if (e.Id == "insiders-info-chat-btn-callback")
{
// Button in #insiders-info that checks whether user has 'insiderChat' role and asks them to confirm granting/revoking it

// Defer
await e.Interaction.DeferAsync(ephemeral: true);

// Get member
var member = await e.Guild.GetMemberAsync(e.User.Id);

// Get insider chat role
var insiderChatRole = await e.Guild.GetRoleAsync(cfgjson.UserRoles.InsiderChat);

// Check whether member already has any insider roles
var insiderRoles = new List<ulong>()
{
cfgjson.UserRoles.InsiderCanary,
cfgjson.UserRoles.InsiderDev,
cfgjson.UserRoles.InsiderBeta,
cfgjson.UserRoles.InsiderRP,
cfgjson.UserRoles.Insider10RP,
cfgjson.UserRoles.PatchTuesday
};
if (member.Roles.Any(x => insiderRoles.Contains(x.Id)))
{
// Member already has an insider role, thus already has access to #insiders
// No need for the chat role too

string insidersMention;
if (cfgjson.InsidersChannel == 0)
insidersMention = "#insiders";
else
insidersMention = $"<#{cfgjson.InsidersChannel}>";

await e.Interaction.CreateFollowupMessageAsync(new DiscordFollowupMessageBuilder()
.WithContent($"You already have Insider roles, so you already have access to chat in {insidersMention}!")
.AsEphemeral(true));

return;
}

if (member.Roles.Contains(insiderChatRole))
FloatingMilkshake marked this conversation as resolved.
Show resolved Hide resolved
{
// Member already has the role
// Ask them if they'd like to remove it
var confirmResponse = new DiscordFollowupMessageBuilder()
.WithContent($"{cfgjson.Emoji.Warning} You already have the {insiderChatRole.Mention} role! Would you like to remove it?")
.AddComponents(new DiscordButtonComponent(DiscordButtonStyle.Danger, "insiders-info-chat-btn-remove-confirm-callback", "Remove"));

await e.Interaction.CreateFollowupMessageAsync(confirmResponse);
}
else
{
// Member does not have the role; show a confirmation message with a button that will give it to them
await e.Interaction.CreateFollowupMessageAsync(new DiscordFollowupMessageBuilder()
.WithContent($"{cfgjson.Emoji.Warning} Please note that <#{cfgjson.InsidersChannel}> is **not for tech support**! If you need tech support, please ask in the appropriate channels instead. Press the button to acknowledge this and get the {insiderChatRole.Mention} role.")
.AddComponents(new DiscordButtonComponent(DiscordButtonStyle.Secondary, "insiders-info-chat-btn-confirm-callback", "I understand")));
}
}
else if (e.Id == "insiders-info-chat-btn-confirm-callback")
{
// Confirmation for granting insiderChat role, see above

// Defer
await e.Interaction.CreateResponseAsync(DiscordInteractionResponseType.DeferredMessageUpdate);

// Give member insider chat role
var member = await e.Guild.GetMemberAsync(e.User.Id);
var insiderChatRole = await e.Guild.GetRoleAsync(cfgjson.UserRoles.InsiderChat);
await member.GrantRoleAsync(insiderChatRole);

// Respond
await e.Interaction.EditFollowupMessageAsync(e.Message.Id, new DiscordWebhookBuilder().WithContent($"{cfgjson.Emoji.Success} You have been given the {insiderChatRole.Mention} role!"));
}
else if (e.Id == "insiders-info-chat-btn-remove-confirm-callback")
{
// Confirmation for revoking insiderChat role, see above

// Defer
await e.Interaction.CreateResponseAsync(DiscordInteractionResponseType.DeferredMessageUpdate);

// Get member
var member = await e.Guild.GetMemberAsync(e.User.Id);

var insiderChatRole = await e.Guild.GetRoleAsync(cfgjson.UserRoles.InsiderChat);
await member.RevokeRoleAsync(insiderChatRole);

await e.Interaction.EditFollowupMessageAsync(e.Message.Id, new DiscordWebhookBuilder().WithContent($"{cfgjson.Emoji.Success} You have been removed from the {insiderChatRole.Mention} role!"));
}
else
{
await e.Interaction.CreateResponseAsync(DiscordInteractionResponseType.ChannelMessageWithSource, new DiscordInteractionResponseBuilder().WithContent("Unknown interaction. I don't know what you are asking me for.").AsEphemeral(true));
Expand Down
16 changes: 15 additions & 1 deletion Structs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -262,12 +262,23 @@ public class ConfigJson

[JsonProperty("forumIntroPosts")]
public List<ulong> ForumIntroPosts { get; private set; } = new();

[JsonProperty("insiderInfoChannel")]
public ulong InsiderInfoChannel { get; private set; }

[JsonProperty("insiderAnnouncementChannel")]
public ulong InsiderAnnouncementChannel { get; private set; } = 0;

private ulong insidersChannel;
[JsonProperty("insidersChannel")]
public ulong InsidersChannel
{
get => insidersChannel == 0 ? InsiderCommandLockedToChannel : insidersChannel;
private set => insidersChannel = value;
}

[JsonProperty("insiderCommandLockedToChannel")]
public ulong InsiderCommandLockedToChannel { get; private set; } = 0;
private ulong InsiderCommandLockedToChannel { get; set; } = 0;

[JsonProperty("dmAutoresponseTimeLimit")]
public int DmAutoresponseTimeLimit { get; private set; } = 0;
Expand Down Expand Up @@ -486,6 +497,9 @@ public class UserRoleConfig

[JsonProperty("insider10RP")]
public ulong Insider10RP { get; private set; }

[JsonProperty("insiderChat")]
public ulong InsiderChat { get; private set; }

[JsonProperty("patchTuesday")]
public ulong PatchTuesday { get; private set; }
Expand Down
4 changes: 3 additions & 1 deletion config.json
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@
"insiderBeta": 643712828217360394,
"insiderRP": 288729239921098752,
"insider10RP": 910319453491839069,
"insiderChat": 1323636793668800547,
"patchTuesday": 445773142233710594,
"giveaways": 1169336992455200820
},
Expand Down Expand Up @@ -315,7 +316,8 @@
1065659890036646019
],
"insiderAnnouncementChannel": 1043898319883219004,
"insiderCommandLockedToChannel": 187649467611086849,
"insiderInfoChannel": 1279201622651572317,
"insidersChannel": 187649467611086849,
FloatingMilkshake marked this conversation as resolved.
Show resolved Hide resolved
"dmAutoresponseTimeLimit": 6,
"autoDeleteEmptyThreads": true,
"insiderCanaryThread": 1082394217168523315,
Expand Down
Loading