Skip to content

Commit

Permalink
Merge pull request #40 from rishabh7923/slash
Browse files Browse the repository at this point in the history
Migrated from Message Commands to Slash Commands
  • Loading branch information
jinx-vi-0 authored Oct 11, 2024
2 parents 6f16aeb + aabd2f8 commit 3a7cf25
Show file tree
Hide file tree
Showing 15 changed files with 431 additions and 300 deletions.
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

0 comments on commit 3a7cf25

Please sign in to comment.