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

Migrated from Message Commands to Slash Commands #40

Merged
merged 1 commit into from
Oct 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
326 changes: 27 additions & 299 deletions bot.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions cache/problems.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[]
19 changes: 19 additions & 0 deletions events/interactionCreate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export default async (interaction, client=interaction.client) => {
if (interaction.isCommand()) {
let command = client.commands.get(interaction.commandName)
if (!command) return;

await command.run(interaction).catch((error) => {
console.log(error)
return interaction.channel.send(`❌ Failed to execute the command due to internal error`)
})
} else if(interaction.isButton()) {
let button = client.buttons.get(interaction.customId.split("_")[0])
if(!button) return;

await button.run(interaction).catch((error) => {
console.log(error)
return interaction.channel.send(`❌ Failed to handle the interaction due to internal error`)
})
}
}
36 changes: 36 additions & 0 deletions events/messageCreate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { REST, Routes } from 'discord.js'

export default async (message, client=message.client) => {
if (message.author.bot) return;

const args = message.content.split(' ');
const command = args[0].toLowerCase();

if (command === ';register') {
if (message.author.id !== process.env.DEVELOPER_ID) return;

let type = args[1]
if (type == 'guild') {
let guildId = args[2] || message.guild.id

const rest = new REST().setToken(process.env.TOKEN);
await rest.put(
Routes.applicationGuildCommands(client.user.id, guildId),
{ body: Array.from(client.commands.values()).map(cmd => cmd.data.toJSON()) }
)
.then(() => message.channel.send(`✅ Added (**${client.commands.size}**) commands in guild (\`${guildId}\`)`))
.catch((error) => message.channel.send(`❌ Failed to register command due to: \`${error}\``))

} else if (type == 'global') {
const rest = new REST().setToken(process.env.TOKEN);
await rest.put(
Routes.applicationCommands(client.user.id),
{ body: Array.from(client.commands.values()).map(cmd => cmd.data.toJSON()) }
)
.then(() => message.channel.send(`✅ Added (${client.commands.size}) commands to all the guilds, it may take time to show in all guilds.`))
.catch((error) => message.channel.send(`❌ Failed to register command due to: \`${error}\``))
} else {
return message.channel.send(`Invalid Syntax, Use \`;register guild/global (guildId:optinal)\``)
}
}
}
24 changes: 24 additions & 0 deletions events/ready.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import cron from 'node-cron'

export default async (client, lc=client.lc) => {
console.log(`Logged in as ${client.user.tag}!`);

cron.schedule('0 6 * * *', async () => {
try {
const daily = await lc.daily();
const channel = client.channels.cache.get(process.env.CHANNEL_ID);
if (channel) {
const questionLink = `https://leetcode.com${daily.link}`;
const response = `@everyone **LeetCode Daily Challenge ${daily.date}:**\n**${daily.question.title}** : ${questionLink}`;
channel.send(response);
} else {
console.error('Channel not found');
}
} catch (error) {
console.error('Error fetching LeetCode daily challenge:', error);
}
}, {
scheduled: true,
timezone: "Asia/Kolkata"
});
}
3 changes: 3 additions & 0 deletions example.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
TOKEN=
CHANNEL_ID=
DEVELOPER_ID=
37 changes: 37 additions & 0 deletions interactions/buttons/topic.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { EmbedBuilder } from "discord.js";

export default {
name: 'topic',
run: async (interaction, lc = interaction.client.lc) => {
const selectedTopic = interaction.customId.replace('topic_', '');
await interaction.deferReply();

const topicQuestions = await lc.problems({
categorySlug: '',
skip: 0,
limit: 300000,
filters: { tags: [selectedTopic] }
});

if (topicQuestions.questions.length === 0) {
await interaction.editReply('No questions found for this topic.');
return;
}

const randomQuestion = topicQuestions.questions[Math.floor(Math.random() * topicQuestions.questions.length)];

const questionLink = `https://leetcode.com/problems/${randomQuestion.titleSlug}/`;
const embed = new EmbedBuilder()
.setTitle(`Random ${selectedTopic.replace(/-/g, ' ')} Question: ${randomQuestion.title}`)
.setURL(questionLink)
.setColor(0x0099FF)
.addFields(
{ name: 'Difficulty', value: randomQuestion.difficulty, inline: true },
{ name: 'Link', value: `[Solve Problem](${questionLink})`, inline: true },
{ name: 'Acceptance Rate', value: `${randomQuestion.acRate.toFixed(2)}%`, inline: true }
)
.setFooter({ text: 'Good luck solving this problem!' });

await interaction.editReply({ embeds: [embed], components: [] });
}
}
17 changes: 17 additions & 0 deletions interactions/commands/help.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { SlashCommandBuilder } from "discord.js";

export default {
data: new SlashCommandBuilder()
.setName('help')
.setDescription('Get the list of commands available for use'),
run: async (interaction) => {
const helpMessage = `**Available Commands:**\n
\`/potd\` - Shows the LeetCode Daily Challenge\n
\`/random [difficulty]\` - Shows a random LeetCode problem (optional: specify difficulty)\n
\`/user <username>\` - Shows user Info\n
\`/streak <username>\` - Shows user Streak Info\n
\`/topics\` - Shows a list of LeetCode topics to choose from\n
\`/help\` - Shows this help message`;
return interaction.reply({ content: helpMessage});
}
}
24 changes: 24 additions & 0 deletions interactions/commands/potd.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { SlashCommandBuilder, EmbedBuilder } from "discord.js";

export default {
data: new SlashCommandBuilder()
.setName('potd')
.setDescription('Shows the LeetCode Daily Challenge'),
run: async (interaction, lc=interaction.client.lc) => {
const daily = await lc.daily();
const questionLink = `https://leetcode.com${daily.link}`;

const embed = new EmbedBuilder()
.setTitle(`LeetCode Daily Challenge - ${daily.date}`)
.setURL(questionLink)
.setDescription(`**${daily.question.title}**`)
.setColor(0xD1006C)
.addFields(
{ name: 'Difficulty', value: daily.question.difficulty, inline: true },
{ name: 'Link', value: `[Click here](${questionLink})`, inline: true }
)
.setFooter({ text: 'Good luck solving today\'s problem!' });

return interaction.reply({ embeds: [embed] });
}
}
53 changes: 53 additions & 0 deletions interactions/commands/random.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { SlashCommandBuilder, EmbedBuilder } from "discord.js";
import LeetCodeUtility from "../../utility/LeetCode.js";
import fs from 'fs'

export default {
data: new SlashCommandBuilder()
.setName('random')
.setDescription('Shows the LeetCode Daily Challenge')
.addStringOption((option) => option
.setName('difficulty')
.setDescription('Mention how hard you want problem to be')
.addChoices({ name: 'easy', value: '1' }, { name: 'medium', value: '2' }, { name: 'hard', value: '3' })
.setRequired(true)
),
run: async (interaction) => {
await interaction.deferReply()
const difficulty = parseInt(interaction.options.getString('difficulty'));

let problems = JSON.parse(fs.readFileSync('./cache/problems.json'))

if (!problems.length) { /** fetch problems and save them */
problems = await LeetCodeUtility.fetchLeetCodeProblems();
fs.writeFileSync('./cache/problems.json', JSON.stringify(problems))
}

let filteredProblems = problems.filter(problem => problem.difficulty.level === difficulty);

if (filteredProblems.length === 0) {
return await interaction
.followUp(`Sorry, I couldn't find any LeetCode problems with the given difficulty level`);
}

const randomIndex = Math.floor(Math.random() * filteredProblems.length);
const problem = filteredProblems[randomIndex].stat;
const questionLink = `https://leetcode.com/problems/${problem.question__title_slug}/`;

const embedColor = difficulty == 1 ? 0x00FF00 : difficulty == 2 ? 0xFFFF00 : 0xFF0000

const embed = new EmbedBuilder()
.setTitle(`${problem.question__title}`)
.setURL(questionLink)
.setColor(embedColor)
.addFields(
{ name: 'Difficulty', value: difficulty === 1 ? 'Easy' : difficulty === 2 ? 'Medium' : 'Hard', inline: true },
{ name: 'Link', value: `[Solve Problem](${questionLink})`, inline: true },
{ name: 'Acceptance Rate', value: `${problem.total_acs} / ${problem.total_submitted} (${(problem.total_acs / problem.total_submitted * 100).toFixed(2)}%)`, inline: true }
)
.setFooter({ text: 'Good luck!' });


return interaction.followUp({ embeds: [ embed ]})
}
}
42 changes: 42 additions & 0 deletions interactions/commands/streak.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { SlashCommandBuilder } from "discord.js";
import LeetCodeUtility from "../../utility/LeetCode.js";


export default {
data: new SlashCommandBuilder()
.setName('streak')
.setDescription('Shows user Streak Info')
.addStringOption(
(option) => option
.setName('username')
.setDescription('Unique username of user')
.setRequired(true)
),
run: async (interaction, lc=interaction.client.lc) => {
await interaction.deferReply()

const username = interaction.options.getString('username')
const user = await lc.user(username);

let streakInfo = 0;
let hasSolvedToday = false;

if (user.matchedUser) {
({ currentStreak: streakInfo, hasSolvedToday } = LeetCodeUtility.calculateStreak(user.matchedUser.submissionCalendar));
}

let streakMessage;
if (streakInfo > 0) {
if (hasSolvedToday) {
streakMessage = `🎉 **${username}** has solved a problem for ${streakInfo} consecutive days! Great work, keep it up! 💪`;
} else {
streakMessage = `⚠️ **${username}** has solved a problem for ${streakInfo} consecutive days! Solve today's problem to maintain your streak and prevent it from resetting! 🔄`;
}
} else {
streakMessage = `❌ **${username}** does not have a streak yet. Start solving problems today to build your streak! 🚀`;
}

return interaction.followUp(streakMessage);

}
}
31 changes: 31 additions & 0 deletions interactions/commands/topics.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { SlashCommandBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } from "discord.js";
import LeetCodeUtility from "../../utility/LeetCode.js";

const topics = [
'Array', 'String', 'Hash Table', 'Dynamic Programming', 'Math',
'Sorting', 'Greedy', 'Depth-First Search', 'Binary Search', 'Database',
'Breadth-First Search', 'Tree', 'Matrix', 'Two Pointers', 'Bit Manipulation',
'Stack', 'Design', 'Heap (Priority Queue)', 'Graph', 'Simulation'
];

export default {
data: new SlashCommandBuilder()
.setName('topics')
.setDescription('Shows a list of LeetCode topics to choose from'),
run: async (interaction) => {
const chunkedTopics = LeetCodeUtility.chunkArray(topics, 5);

const rows = chunkedTopics.map(chunk =>
new ActionRowBuilder().addComponents(
chunk.map(topic =>
new ButtonBuilder()
.setCustomId(`topic_${topic.toLowerCase().replace(/\s+/g, '-')}`)
.setLabel(topic)
.setStyle(ButtonStyle.Secondary)
)
)
);

return interaction.reply({ content: 'Choose a topic to get a random question:', components: rows })
}
}
60 changes: 60 additions & 0 deletions interactions/commands/user.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { SlashCommandBuilder, EmbedBuilder } from "discord.js";

export default {
data: new SlashCommandBuilder()
.setName('user')
.setDescription(' Shows user Info')
.addStringOption(
(option) => option
.setName('username')
.setDescription('Unique username of user')
.setRequired(true)
),
run: async (interaction, lc=interaction.client.lc) => {
await interaction.deferReply()
const username = interaction.options.getString('username')

const [userInfo, contestInfo] = await Promise.all([
lc.user(username),
lc.user_contest_info(username)
]);

if (!userInfo.matchedUser) {
return interaction.followUp({ content: `User "${username}" not found.`});
}

const user = userInfo.matchedUser;
const profile = user.profile;
const submitStats = user.submitStats;

const embed = new EmbedBuilder()
.setColor('#FFD700') // Gold color for the embed
.setTitle(`LeetCode Profile: **${username}**`)
.setThumbnail(profile.userAvatar)
.addFields(
{ name: '👤 Real Name', value: profile.realName || '*Not provided*', inline: true },
{ name: '🏆 Ranking', value: profile.ranking ? profile.ranking.toString() : '*Not ranked*', inline: true },
{ name: '🌍 Country', value: profile.countryName || '*Not provided*', inline: true },
{ name: '🏢 Company', value: profile.company || '*Not provided*', inline: true },
{ name: '🎓 School', value: profile.school || '*Not provided*', inline: true },
{ name: '\u200B', value: '⬇️ **Problem Solving Stats**', inline: false },
{ name: '🟢 Easy', value: `Solved: ${submitStats.acSubmissionNum[1].count} / ${submitStats.totalSubmissionNum[1].count}`, inline: true },
{ name: '🟠 Medium', value: `Solved: ${submitStats.acSubmissionNum[2].count} / ${submitStats.totalSubmissionNum[2].count}`, inline: true },
{ name: '🔴 Hard', value: `Solved: ${submitStats.acSubmissionNum[3].count} / ${submitStats.totalSubmissionNum[3].count}`, inline: true },
{ name: '📊 Total', value: `Solved: ${submitStats.acSubmissionNum[0].count} / ${submitStats.totalSubmissionNum[0].count}`, inline: true }
);

if (contestInfo.userContestRanking) {
embed.addFields(
{ name: '🚩 **Contest Info**', value: `\`\`\`Rating: ${Math.round(contestInfo.userContestRanking.rating)}\nRanking: ${contestInfo.userContestRanking.globalRanking}\nTop: ${contestInfo.userContestRanking.topPercentage.toFixed(2)}%\nAttended: ${contestInfo.userContestRanking.attendedContestsCount}\`\`\`` }
);
}

if (user.badges && user.badges.length > 0) {
const badgeNames = user.badges.map(badge => badge.displayName).join('\n•');
embed.addFields({ name: '🏅 Badges', value: "•" + badgeNames, inline: false });
}

return interaction.followUp({ embeds: [ embed ]})
}
}
2 changes: 1 addition & 1 deletion keep_alive.js → utility/KeepAlive.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ export default function keepAlive() {
}).listen(8080);

console.log("Server is running on port 8080");
}
}
Loading