Skip to content

Commit c740663

Browse files
updated fixes on codebility discord bot
1 parent 94f37cc commit c740663

28 files changed

Lines changed: 3822 additions & 130 deletions

‎.gitignore‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ node_modules/
3636
npm-debug.log
3737
yarn-error.log
3838

39+
## Ignore environment variable files
40+
apps/bot/.env
41+
3942
# BUCK
4043
buck-out/
4144
\.buckd/

‎apps/bot/.gitignore‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
## Ignore environment variable files
2+
apps/bot/.env

‎apps/bot/.npmrc‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
shamefully-hoist=true
2+
public-hoist-pattern[]=*

‎apps/bot/commands/badges.ts‎

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
// apps/bot/commands/badges.ts
2+
import {
3+
SlashCommandBuilder,
4+
ChatInputCommandInteraction,
5+
EmbedBuilder,
6+
PermissionFlagsBits,
7+
MessageFlags,
8+
} from "discord.js";
9+
import { supabaseBot } from "../utils/supabase.bot";
10+
11+
//Slash command
12+
const slashCommand = new SlashCommandBuilder()
13+
.setName("badges")
14+
.setDescription("View your badges or award a badge (admin only).")
15+
.addUserOption((opt) =>
16+
opt
17+
.setName("user")
18+
.setDescription("Target user to view or award badges to.")
19+
.setRequired(false)
20+
)
21+
.addStringOption((opt) =>
22+
opt
23+
.setName("badge")
24+
.setDescription("Badge ID to award (admin only)")
25+
.setRequired(false)
26+
);
27+
28+
// Export the command
29+
export const command = {
30+
name: "badges",
31+
data: slashCommand,
32+
};
33+
34+
// Execute function
35+
export const execute = async (interaction: ChatInputCommandInteraction) => {
36+
try {
37+
await interaction.deferReply();
38+
39+
const userOption = interaction.options.getUser("user");
40+
const badgeId = interaction.options.getString("badge");
41+
const targetUser = userOption || interaction.user;
42+
43+
// Ensure users_discord record exists for target user
44+
const { error: userError } = await supabaseBot
45+
.from("users_discord")
46+
.upsert(
47+
{
48+
id: targetUser.id,
49+
username: targetUser.username,
50+
avatar_url: targetUser.displayAvatarURL(),
51+
discriminator: targetUser.discriminator || "0",
52+
},
53+
{ onConflict: "id" }
54+
);
55+
56+
if (userError) {
57+
console.error("❌ Error ensuring user record:", userError.message);
58+
}
59+
60+
// ===== ADMIN AWARD FLOW =====
61+
if (badgeId) {
62+
// Permission check
63+
if (
64+
!interaction.memberPermissions?.has(PermissionFlagsBits.ManageGuild)
65+
) {
66+
await interaction.editReply({
67+
content:
68+
"❌ You do not have permission to award badges. (Requires **Manage Server** permission.)",
69+
});
70+
return;
71+
}
72+
73+
// Validate badgeId
74+
const badgeIdNum = parseInt(badgeId);
75+
if (isNaN(badgeIdNum)) {
76+
await interaction.editReply({
77+
content: "❌ Invalid badge ID. Please provide a valid **number**.",
78+
});
79+
return;
80+
}
81+
82+
// Check if badge exists
83+
const { data: badge, error: badgeError } = await supabaseBot
84+
.from("badges_discord")
85+
.select("id, badge_name")
86+
.eq("id", badgeIdNum)
87+
.single();
88+
89+
if (badgeError || !badge) {
90+
await interaction.editReply({
91+
content: `❌ No badge found with ID **${badgeIdNum}**.`,
92+
});
93+
return;
94+
}
95+
96+
// Check if user already has this badge
97+
const { data: existingBadge } = await supabaseBot
98+
.from("user_badges_discord")
99+
.select("id")
100+
.eq("user_id", targetUser.id)
101+
.eq("badge_id", badgeIdNum)
102+
.maybeSingle();
103+
104+
if (existingBadge) {
105+
await interaction.editReply({
106+
content: `⚠️ <@${targetUser.id}> already has the badge **${badge.badge_name}**.`,
107+
});
108+
return;
109+
}
110+
111+
// Award the badge
112+
const { error: insertError } = await supabaseBot
113+
.from("user_badges_discord")
114+
.insert({
115+
user_id: targetUser.id,
116+
badge_id: badgeIdNum,
117+
});
118+
119+
if (insertError) {
120+
console.error("❌ Error awarding badge:", insertError.message);
121+
await interaction.editReply({
122+
content: "⚠️ Failed to award badge. Please try again later.",
123+
});
124+
return;
125+
}
126+
127+
await interaction.editReply({
128+
content: `🏅 **${badge.badge_name}** has been awarded to <@${targetUser.id}>!`,
129+
});
130+
131+
console.log(
132+
`✅ Badge ${badgeIdNum} awarded to ${targetUser.username} by ${interaction.user.username}`
133+
);
134+
return;
135+
}
136+
137+
// ===== VIEW BADGES FLOW =====
138+
const { data: userBadges, error: ubError } = await supabaseBot
139+
.from("user_badges_discord")
140+
.select("badge_id, awarded_at")
141+
.eq("user_id", targetUser.id);
142+
143+
if (ubError) {
144+
console.error("❌ Error fetching user badges:", ubError.message);
145+
await interaction.editReply({
146+
content: "⚠️ Failed to fetch user badges. Please try again.",
147+
});
148+
return;
149+
}
150+
151+
if (!userBadges || userBadges.length === 0) {
152+
await interaction.editReply({
153+
content: `<@${targetUser.id}> has no badges yet. 🏆`,
154+
});
155+
return;
156+
}
157+
158+
// Fetch all badge details from badges_discord
159+
const { data: allBadges, error: allBadgesError } = await supabaseBot
160+
.from("badges_discord")
161+
.select("id, badge_name, badge_icon_url, description");
162+
163+
if (allBadgesError || !allBadges) {
164+
console.error("❌ Error fetching badge details:", allBadgesError.message);
165+
await interaction.editReply({
166+
content: "⚠️ Failed to load badge details. Please try again.",
167+
});
168+
return;
169+
}
170+
171+
// Merge badge info
172+
const badgesWithDetails = userBadges
173+
.map((ub) => {
174+
const badge = allBadges.find((b) => b.id === ub.badge_id);
175+
if (!badge) return null;
176+
return {
177+
badge_name: badge.badge_name,
178+
badge_icon_url: badge.badge_icon_url,
179+
description: badge.description,
180+
awarded_at: ub.awarded_at,
181+
};
182+
})
183+
.filter((badge): badge is NonNullable<typeof badge> => badge !== null);
184+
185+
// ===== CREATE EMBED =====
186+
const embed = new EmbedBuilder()
187+
.setTitle(`${targetUser.username}'s Badges`)
188+
.setColor("#FFD700")
189+
.setThumbnail(targetUser.displayAvatarURL())
190+
.setFooter({ text: `Total Badges: ${badgesWithDetails.length}` })
191+
.setTimestamp();
192+
193+
if (badgesWithDetails.length > 0) {
194+
embed.setDescription(
195+
badgesWithDetails
196+
.map(
197+
(b) =>
198+
`🏅 **${b.badge_name}**\n${b.description ?? "No description."}`
199+
)
200+
.join("\n\n")
201+
);
202+
} else {
203+
embed.setDescription("No badges found.");
204+
}
205+
206+
await interaction.editReply({ embeds: [embed] });
207+
} catch (error) {
208+
console.error("❌ Error executing badges command:", error);
209+
210+
const errorMessage = {
211+
content:
212+
"❌ An unexpected error occurred while processing this command. Please try again later.",
213+
};
214+
215+
if (interaction.deferred) {
216+
await interaction.editReply(errorMessage);
217+
} else if (!interaction.replied) {
218+
await interaction.reply({
219+
...errorMessage,
220+
flags: [MessageFlags.Ephemeral],
221+
});
222+
}
223+
}
224+
};

0 commit comments

Comments
 (0)