Skip to content

Commit 868c7bc

Browse files
authored
Merge pull request #456 from CodebilityDev/dev
nov changes
2 parents 74313c7 + f0bbbd4 commit 868c7bc

153 files changed

Lines changed: 28349 additions & 4585 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.gitignore‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ node_modules/
3636
npm-debug.log
3737
yarn-error.log
3838

39+
## Ignore environment variable files
40+
apps/bot/.env
41+
42+
.next
43+
3944
# BUCK
4045
buck-out/
4146
\.buckd/
@@ -65,6 +70,7 @@ web-build/
6570
**/*/.expo
6671

6772
**/*/.next
73+
packages/ui/dist/*
6874

6975
**/*/ios
7076
**/*/android
@@ -81,4 +87,5 @@ build/**
8187
!.yarn/sdks
8288
!.yarn/versions
8389

84-
dist
90+
distapps/bot/.env
91+
.env

‎.npmrc‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
use-lockfile=true
22
shared-workspace-lockfile=true
3-
3+
public-hoist-pattern[]=*@types/react*

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

0 commit comments

Comments
 (0)