Skip to content

Commit fcc0789

Browse files
hmd-aliwiktoriavh
andauthored
feat: add functionality to give role to users (#25)
* πŸ”¨ refactor: run check script (organize imports) * 🌟 feat: add onboarding channelId and roleId to env * 🌟 feat: add onboarding component with button for role addition * 🌟 feat: add onboarding command * πŸ€– ci: add onboarding channelId and roleId to deployment script * 🌟 feat: update web-features dependency to version 3.7.0 and add 'open-closed' to NON_BASELINE_FEATURES * refactor: use addRoleToUser utility for role assignment * chore: add comment to clarify that export is later * chore: should not be part of the curren active commands * fix: improve error handling in role assignment * Change onboarding channel and role IDs to optional * remove duplicate vars in example env --------- Co-authored-by: Wiktoria Van Harneveldt <[email protected]> Co-authored-by: Wiktoria van Harneveldt <[email protected]>
1 parent 2649c2d commit fcc0789

File tree

5 files changed

+119
-0
lines changed

5 files changed

+119
-0
lines changed

β€Ž.env.exampleβ€Ž

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@ SERVER_ID=your_server_id_here
99
GUIDES_CHANNEL_ID=your_guides_channel_id_here
1010
ADVENT_OF_CODE_CHANNEL_ID=your_advent_of_code_forum_channel_id_here
1111
REPEL_LOG_CHANNEL_ID=your_repel_log_channel_id_here
12+
ONBOARDING_CHANNEL_ID=onboarding_channel_id_here
1213

1314
# Role IDs (REQUIRED)
1415
MODERATORS_ROLE_IDS=role_id_1,role_id_2,role_id_3
1516
REPEL_ROLE_ID=your_repel_role_id_here
17+
ONBOARDING_ROLE_ID=onboarding_role_id_here
1618

1719
# Optional Role IDs
1820
# ROLE_A_ID=optional_role_a_id
@@ -24,3 +26,6 @@ REPEL_ROLE_ID=your_repel_role_id_here
2426
# Docker deployments should use /app/data for persistence
2527
GUIDES_TRACKER_PATH=guides-tracker.json
2628
ADVENT_OF_CODE_TRACKER_PATH=advent-of-code-tracker.json
29+
30+
31+
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import {
2+
ActionRowBuilder,
3+
ButtonBuilder,
4+
ButtonStyle,
5+
ContainerBuilder,
6+
type MessageActionRowComponentBuilder,
7+
} from 'discord.js';
8+
9+
// Exported at the bottom after all child components are added
10+
const containerComponent = new ContainerBuilder();
11+
12+
const actionRowComponent = new ActionRowBuilder<MessageActionRowComponentBuilder>();
13+
14+
const buttonComponent = new ButtonBuilder()
15+
.setCustomId('onboarding_add_role')
16+
.setLabel('Add role')
17+
.setStyle(ButtonStyle.Primary);
18+
19+
actionRowComponent.addComponents(buttonComponent);
20+
containerComponent.addActionRowComponents(actionRowComponent);
21+
22+
export { containerComponent };
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { ApplicationCommandType, MessageFlags } from 'discord.js';
2+
import { config } from '../../env.js';
3+
import { addRoleToUser } from '../../util/addRoleToUser.js';
4+
import { createCommand } from '../../util/commands.js';
5+
import { containerComponent } from './component.js';
6+
7+
export const onboardingCommand = createCommand({
8+
data: {
9+
name: 'onboarding',
10+
description: 'Manage onboarding settings',
11+
type: ApplicationCommandType.ChatInput,
12+
},
13+
execute: async (interaction) => {
14+
const guild = interaction.guild;
15+
if (!guild) {
16+
await interaction.reply({
17+
content: 'This command can only be used in a server.',
18+
flags: MessageFlags.Ephemeral,
19+
});
20+
return;
21+
}
22+
const onboardingRole = guild.roles.cache.get(config.onboarding.roleId);
23+
if (!onboardingRole) {
24+
await interaction.reply({
25+
content: 'Onboarding role not found. Please check the configuration.',
26+
flags: MessageFlags.Ephemeral,
27+
});
28+
return;
29+
}
30+
const onboardingChannel = guild.channels.cache.get(config.onboarding.channelId);
31+
if (!onboardingChannel || !onboardingChannel.isSendable()) {
32+
await interaction.reply({
33+
content:
34+
'Onboarding channel not found or is not a text channel. Please check the configuration.',
35+
flags: MessageFlags.Ephemeral,
36+
});
37+
return;
38+
}
39+
40+
const onboardingMessage = await interaction.reply({
41+
components: [containerComponent],
42+
flags: MessageFlags.IsComponentsV2,
43+
});
44+
45+
const collector = onboardingMessage.createMessageComponentCollector({});
46+
47+
collector.on('collect', async (componentInteraction) => {
48+
if (componentInteraction.customId === 'onboarding_add_role') {
49+
try {
50+
const member = await guild.members.fetch(componentInteraction.user.id);
51+
await addRoleToUser(member, onboardingRole);
52+
53+
await componentInteraction.reply({
54+
content: `You have been given the ${onboardingRole.name} role!`,
55+
flags: MessageFlags.Ephemeral,
56+
});
57+
} catch (error) {
58+
await componentInteraction.reply({
59+
content: `Failed to add role. Please contact an administrator.`,
60+
flags: MessageFlags.Ephemeral,
61+
});
62+
console.error('Error adding role:\n', error);
63+
}
64+
}
65+
});
66+
},
67+
});

β€Žsrc/env.tsβ€Ž

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,17 @@ export const config = {
3838
guides: requireEnv('GUIDES_CHANNEL_ID'),
3939
adventOfCode: requireEnv('ADVENT_OF_CODE_CHANNEL_ID'),
4040
},
41+
onboarding: {
42+
channelId: optionalEnv('ONBOARDING_CHANNEL_ID'),
43+
roleId: optionalEnv('ONBOARDING_ROLE_ID'),
44+
},
45+
// Add more config sections as needed:
46+
// database: {
47+
// url: requireEnv('DATABASE_URL'),
48+
// },
49+
// api: {
50+
// openaiKey: optionalEnv('OPENAI_API_KEY'),
51+
// },
4152
};
4253

4354
export type Config = typeof config;

β€Žsrc/util/addRoleToUser.tsβ€Ž

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import type { GuildMember, Role } from 'discord.js';
2+
3+
export async function addRoleToUser(member: GuildMember, role: Role): Promise<void> {
4+
const hasRole = member.roles.cache.has(role.id);
5+
if (!hasRole) {
6+
try {
7+
await member.roles.add(role);
8+
} catch (error) {
9+
throw new Error(
10+
`Failed to add role "${role.name}" to user "${member.user.username}": ${error instanceof Error ? error.message : String(error)}`
11+
);
12+
}
13+
}
14+
}

0 commit comments

Comments
Β (0)