├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ ├── custom.md
│ └── feature_request.md
└── workflows
│ └── greetings.yml
├── .gitignore
├── .vscode
├── c_cpp_properties.json
├── launch.json
└── settings.json
├── LICENSE
├── README.md
├── bot.js
├── cache
└── problems.json
├── events
├── interactionCreate.js
├── messageCreate.js
└── ready.js
├── example.env
├── interactions
├── buttons
│ └── topic.js
└── commands
│ ├── contests.js
│ ├── help.js
│ ├── potd.js
│ ├── random.js
│ ├── streak.js
│ ├── topics.js
│ └── user.js
├── package-lock.json
├── package.json
└── utility
├── KeepAlive.js
└── LeetCode.js
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Desktop (please complete the following information):**
27 | - OS: [e.g. iOS]
28 | - Browser [e.g. chrome, safari]
29 | - Version [e.g. 22]
30 |
31 | **Smartphone (please complete the following information):**
32 | - Device: [e.g. iPhone6]
33 | - OS: [e.g. iOS8.1]
34 | - Browser [e.g. stock browser, safari]
35 | - Version [e.g. 22]
36 |
37 | **Additional context**
38 | Add any other context about the problem here.
39 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/custom.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Custom issue template
3 | about: Describe this issue template's purpose here.
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.github/workflows/greetings.yml:
--------------------------------------------------------------------------------
1 | name: Greetings
2 |
3 | on:
4 | pull_request_target:
5 | types: [opened, reopened, closed]
6 | issues:
7 | types: [opened, closed]
8 |
9 | jobs:
10 | greeting:
11 | runs-on: ubuntu-latest
12 | permissions:
13 | issues: write
14 | pull-requests: write
15 | steps:
16 | - uses: actions/first-interaction@v1
17 | with:
18 | repo-token: ${{ secrets.GITHUB_TOKEN }}
19 | issue-message: "👋 Hey there, rockstar! Thanks for dropping an issue! The DDoL team is on it like pineapple on pizza (love it or hate it). Stick around, magic's about to happen!"
20 | pr-message: "🎉 Boom! Your pull request just flew into the DDoL HQ. High fives all around! Our team of tech wizards will check it out and get back to you faster than you can say 'code ninja!' Thanks for leveling up the project!"
21 |
22 | congratulate:
23 | if: github.event.action == 'closed' && github.event.issue != null
24 | runs-on: ubuntu-latest
25 | permissions:
26 | issues: write
27 | steps:
28 | - name: Congratulate on Issue Closure
29 | run: |
30 | curl -X POST \
31 | -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
32 | -H "Accept: application/vnd.github.v3+json" \
33 | https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.issue.number }}/comments \
34 | -d '{"body": "🎉 Congratulations @'${{ github.event.issue.user.login }}'! Your issue has been successfully closed! Thanks for your contribution! If you enjoyed contributing, please consider giving us a ⭐ and following us for updates!"}'
35 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .env
--------------------------------------------------------------------------------
/.vscode/c_cpp_properties.json:
--------------------------------------------------------------------------------
1 | {
2 | "configurations": [
3 | {
4 | "name": "linux-gcc-x64",
5 | "includePath": [
6 | "${workspaceFolder}/**"
7 | ],
8 | "compilerPath": "/usr/bin/gcc",
9 | "cStandard": "${default}",
10 | "cppStandard": "${default}",
11 | "intelliSenseMode": "linux-gcc-x64",
12 | "compilerArgs": [
13 | ""
14 | ]
15 | }
16 | ],
17 | "version": 4
18 | }
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "name": "C/C++ Runner: Debug Session",
6 | "type": "cppdbg",
7 | "request": "launch",
8 | "args": [],
9 | "stopAtEntry": false,
10 | "externalConsole": false,
11 | "cwd": "/home/pred/go",
12 | "program": "/home/pred/go/main.cpp",
13 | "MIMode": "gdb",
14 | "miDebuggerPath": "gdb",
15 | "setupCommands": [
16 | {
17 | "description": "Enable pretty-printing for gdb",
18 | "text": "-enable-pretty-printing",
19 | "ignoreFailures": true
20 | }
21 | ]
22 | }
23 | ]
24 | }
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "C_Cpp_Runner.cCompilerPath": "gcc",
3 | "C_Cpp_Runner.cppCompilerPath": "g++",
4 | "C_Cpp_Runner.debuggerPath": "gdb",
5 | "C_Cpp_Runner.cStandard": "",
6 | "C_Cpp_Runner.cppStandard": "",
7 | "C_Cpp_Runner.msvcBatchPath": "C:/Program Files/Microsoft Visual Studio/VR_NR/Community/VC/Auxiliary/Build/vcvarsall.bat",
8 | "C_Cpp_Runner.useMsvc": false,
9 | "C_Cpp_Runner.warnings": [
10 | "-Wall",
11 | "-Wextra",
12 | "-Wpedantic",
13 | "-Wshadow",
14 | "-Wformat=2",
15 | "-Wcast-align",
16 | "-Wconversion",
17 | "-Wsign-conversion",
18 | "-Wnull-dereference"
19 | ],
20 | "C_Cpp_Runner.msvcWarnings": [
21 | "/W4",
22 | "/permissive-",
23 | "/w14242",
24 | "/w14287",
25 | "/w14296",
26 | "/w14311",
27 | "/w14826",
28 | "/w44062",
29 | "/w44242",
30 | "/w14905",
31 | "/w14906",
32 | "/w14263",
33 | "/w44265",
34 | "/w14928"
35 | ],
36 | "C_Cpp_Runner.enableWarnings": true,
37 | "C_Cpp_Runner.warningsAsError": false,
38 | "C_Cpp_Runner.compilerArgs": [],
39 | "C_Cpp_Runner.linkerArgs": [],
40 | "C_Cpp_Runner.includePaths": [],
41 | "C_Cpp_Runner.includeSearch": [
42 | "*",
43 | "**/*"
44 | ],
45 | "C_Cpp_Runner.excludeSearch": [
46 | "**/build",
47 | "**/build/**",
48 | "**/.*",
49 | "**/.*/**",
50 | "**/.vscode",
51 | "**/.vscode/**"
52 | ],
53 | "C_Cpp_Runner.useAddressSanitizer": false,
54 | "C_Cpp_Runner.useUndefinedSanitizer": false,
55 | "C_Cpp_Runner.useLeakSanitizer": false,
56 | "C_Cpp_Runner.showCompilationTime": false,
57 | "C_Cpp_Runner.useLinkTimeOptimization": false,
58 | "C_Cpp_Runner.msvcSecureNoWarnings": false
59 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Adarsh
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | #

Daily Dose of Leetcode

5 |
6 | 
7 | [](https://opensource.org/licenses/MIT)
8 | 
9 |
10 | [](https://discord.com/oauth2/authorize?client_id=1260183706866552852&permissions=274878057472&integration_type=0&scope=bot)
11 | [](https://discord.gg/Rxx6p5je)
12 |
13 |

14 |
15 |
16 |
17 | ##
Introduction
18 |
19 | This Discord bot integrates with LeetCode to provide daily challenges, random problems, user information, and more. It uses `discord.js` to interact with the Discord API and `leetcode-query` to fetch data from LeetCode.
20 |
21 | ---
22 |
23 | ## ✨ Features
24 |
25 | - Daily LeetCode Challenge notification
26 | - Delivers a random LeetCode problem based on difficulty
27 | - Fetches user profile information
28 | - Displays user streak information
29 | - Provides a user-friendly help command
30 |
31 | ---
32 |
33 | ## 📒 Commands
34 | The bot does not use the traditional message command approach; instead, it uses a slash command approach. This means that when you type `/`, you will see a list of available commands in the guild. The commands provided by this bot are:
35 | - `/potd` - Shows the LeetCode Daily Challenge
36 | - `/random ` - Shows a random LeetCode problem
37 | - `/user ` - Shows User Info
38 | - `/streak ` - Shows user Streak Info
39 | - `/help` - Shows help message
40 | - `/topic` - Shows a list of LeetCode topics to choose from
41 |
42 | ---
43 |
44 | ## ⚙️ Installation
45 |
46 | 1. 📂 Clone the repository:
47 |
48 | ```bash
49 | git clone https://github.com/your-github-username/leetcode-discord-bot.git
50 | ```
51 |
52 | 2. 📂 Navigate to the project directory:
53 |
54 | ```bash
55 | cd DDoL
56 | ```
57 |
58 | 3. 🛠 Install the dependencies:
59 |
60 | ```bash
61 | npm install
62 | ```
63 |
64 | 4. 🔐 Create a `.env` file in the root directory and add your Discord bot token and channel ID:
65 |
66 | ```plaintext
67 | TOKEN=your-discord-bot-token
68 | CHANNEL_ID=your-channel-id
69 | DEVELOPER_ID=your-discord-id
70 | ```
71 |
72 | ---
73 |
74 | ## 🛠️ How to Set Up a Bot on Discord
75 |
76 | 1. Create a new application
77 | Go to the [Discord Developer Portal](https://discord.com/developers/applications?new_application=true) and create a new application.
78 |
79 | 2. Configure `OAuth2` 🔑
80 |
81 | 3. In your app's settings, navigate to OAuth2.
82 | - Open the OAuth2 URL Generator and select the bot option.
83 | - Under Bot Permissions, select Administrator.
84 | - Set up the bot
85 |
86 | 4. Go to the `Bot` section and enable the following gateway intents:
87 | - `Server Members Intent`
88 | - `Message Content Intent`
89 | - Get your bot's `TOKEN` 🔐 and Create a Discord server
90 |
91 | 5. Create a new server in Discord and retrieve the `Channel ID` where your bot will operate.
92 |
93 | ---
94 |
95 | ## 🚀 Usage
96 |
97 | 1. Start the bot 🤖:
98 |
99 | ```bash
100 | npm run start
101 | ```
102 |
103 | 2. Invite the bot to your Discord server using the OAuth2 URL generated from the Discord Developer Portal.
104 |
105 | 3. Use the commands in any channel the bot has access to.
106 | 4. Register slash commands in your guild using `;register guild `. Please don't provide the server ID if you want to register commands in your current guild.
107 | 5. If you want to register slash commands in all guilds, ignore the 4th step and run `;register global`
108 | ---
109 |
110 | ## ✨ Contributing
111 |
112 | Contributions are welcome! Please raise an issue with your changes.
113 |
114 | ### 🤝 How to Contribute
115 | We appreciate your contributions! To get involved, follow these simple steps:
116 |
117 | 1. Fork the repository 🍴
118 | Make a personal copy of the repository.
119 |
120 | 2. Create a new branch 🌿
121 | Start working on your changes in a separate branch for better tracking.
122 |
123 | 3. Make your changes 🛠️
124 | Update the code, fix bugs, or add new features!
125 |
126 | 4. Commit and push your changes 💻
127 | Make sure to include a clear commit message explaining what you’ve done.
128 |
129 | 5. Submit a Pull Request (PR) 🪄
130 | Once you're ready, open a PR for review.
131 |
132 | Done! ✅
133 | That's it—thanks for contributing! We’ll review your PR as soon as possible.
134 |
135 |
136 |
137 | ### ❤️ Our Valuable Contributors
138 | [](https://github.com/jinx-vi-0/DDoL/graphs/contributors)
139 |
140 | ---
141 |
142 | ## 🪄 License
143 |
144 | This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
145 |
146 | Don't forget to give us a ⭐
file.endsWith('.js')).forEach(file => {
31 | import(`./interactions/commands/${file}`).then(({ default: command }) => {
32 | client.commands.set(command.data.name, command);
33 | }).catch(error => console.error(`Failed to load command ${file}:`, error));
34 | });
35 |
36 | fs.readdirSync('./interactions/buttons').filter(file => file.endsWith('.js')).forEach(file => {
37 | import(`./interactions/buttons/${file}`).then(({ default: button }) => {
38 | client.buttons.set(button.name, button);
39 | }).catch(error => console.error(`Failed to load button ${file}:`, error));
40 | });
41 |
42 |
43 | /**
44 | * Load all events
45 | */
46 | fs.readdirSync('./events').filter(file => file.endsWith('.js')).forEach((file) => {
47 | import(`./events/${file}`).then(({ default: event }) => {
48 | client.on(file.split(".")[0], (...args) => event(...args))
49 | })
50 | })
51 |
52 | KeepAlive();
53 |
54 | client.login(process.env.TOKEN);
55 |
--------------------------------------------------------------------------------
/cache/problems.json:
--------------------------------------------------------------------------------
1 | []
--------------------------------------------------------------------------------
/events/interactionCreate.js:
--------------------------------------------------------------------------------
1 | export default async (interaction, client=interaction.client) => {
2 | if (interaction.isCommand()) {
3 | let command = client.commands.get(interaction.commandName)
4 | if (!command) return;
5 |
6 | await command.run(interaction).catch((error) => {
7 | console.log(error)
8 | return interaction.channel.send(`❌ Failed to execute the command due to internal error`)
9 | })
10 | } else if(interaction.isButton()) {
11 | let button = client.buttons.get(interaction.customId.split("_")[0])
12 | if(!button) return;
13 |
14 | await button.run(interaction).catch((error) => {
15 | console.log(error)
16 | return interaction.channel.send(`❌ Failed to handle the interaction due to internal error`)
17 | })
18 | }
19 | }
--------------------------------------------------------------------------------
/events/messageCreate.js:
--------------------------------------------------------------------------------
1 | import { REST, Routes } from 'discord.js'
2 |
3 | export default async (message, client=message.client) => {
4 | if (message.author.bot) return;
5 |
6 | const args = message.content.split(' ');
7 | const command = args[0].toLowerCase();
8 |
9 | if (command === ';register') {
10 | if (message.author.id !== process.env.DEVELOPER_ID) return;
11 |
12 | let type = args[1]
13 | if (type == 'guild') {
14 | let guildId = args[2] || message.guild.id
15 |
16 | const rest = new REST().setToken(process.env.TOKEN);
17 | await rest.put(
18 | Routes.applicationGuildCommands(client.user.id, guildId),
19 | { body: Array.from(client.commands.values()).map(cmd => cmd.data.toJSON()) }
20 | )
21 | .then(() => message.channel.send(`✅ Added (**${client.commands.size}**) commands in guild (\`${guildId}\`)`))
22 | .catch((error) => message.channel.send(`❌ Failed to register command due to: \`${error}\``))
23 |
24 | } else if (type == 'global') {
25 | const rest = new REST().setToken(process.env.TOKEN);
26 | await rest.put(
27 | Routes.applicationCommands(client.user.id),
28 | { body: Array.from(client.commands.values()).map(cmd => cmd.data.toJSON()) }
29 | )
30 | .then(() => message.channel.send(`✅ Added (${client.commands.size}) commands to all the guilds, it may take time to show in all guilds.`))
31 | .catch((error) => message.channel.send(`❌ Failed to register command due to: \`${error}\``))
32 | } else {
33 | return message.channel.send(`Invalid Syntax, Use \`;register guild/global (guildId:optinal)\``)
34 | }
35 | }
36 | }
--------------------------------------------------------------------------------
/events/ready.js:
--------------------------------------------------------------------------------
1 | import { EmbedBuilder, bold } from "discord.js";
2 | import cron from 'node-cron'
3 | import axios from "axios";
4 |
5 |
6 | export default async (client, lc = client.lc) => {
7 | console.log(`Logged in as ${client.user.tag}!`);
8 |
9 | cron.schedule('0 6 * * *', async () => {
10 | const channel = client.channels.cache.get(process.env.CHANNEL_ID);
11 | if (!channel) return console.error(`Failed to find channel for daily challenge (${channel})`)
12 |
13 | const { question, link, date } = await lc.daily();
14 |
15 | const embed = new EmbedBuilder()
16 | .setTitle(question.title)
17 | .setURL(`https://leetcode.com${link}`)
18 | .setColor(0xfea116)
19 | .addFields(
20 | { name: 'Difficulty', value: question.difficulty, inline: true },
21 | { name: 'Link', value: `[Click Here](https://leetcode.com${link})`, inline: true },
22 | /* Show 3 topics in each line for better formatting */
23 | { name: 'Topic', value: question.topicTags.map((x, i) => (i % 3 === 0 && i !== 0 ? '\n' : '') + x.name).join(', ') }
24 | )
25 |
26 | return channel.send({
27 | content: bold(`@everyone, LeetCode Daily Challenge: \`${date}\``),
28 | embeds: [embed]
29 | })
30 | }, {
31 | scheduled: true,
32 | timezone: "Asia/Kolkata"
33 | })
34 |
35 | // New function to fetch upcoming contests
36 | async function getUpcomingContests() {
37 | try {
38 | const response = await axios.post('https://leetcode.com/graphql', {
39 | query: `{
40 | topTwoContests {
41 | title
42 | startTime
43 | duration
44 | }
45 | }`
46 | }, {
47 | headers: {
48 | 'Content-Type': 'application/json',
49 | }
50 | });
51 |
52 |
53 | return response.data.data.topTwoContests.map(contest => ({
54 | ...contest,
55 | start_time: new Date(contest.startTime * 1000),
56 | href: `https://leetcode.com/contest/${contest.title.toLowerCase().replace(/\s+/g, '-')}`
57 | }));
58 | } catch (error) {
59 | console.error('Failed to fetch upcoming contests:', error);
60 | return [];
61 | }
62 | }
63 |
64 |
65 | // Function to schedule reminders for contests
66 | async function scheduleContestReminders() {
67 | const contests = await getUpcomingContests();
68 | // console.log("Working" , contests);
69 | contests.forEach(contest => {
70 | const reminderTime = new Date(contest.start_time.getTime() - 60 * 60 * 1000); // 1 hour before
71 | const now = new Date();
72 |
73 |
74 | if (reminderTime > now) {
75 | const delay = reminderTime.getTime() - now.getTime();
76 | console.log("Original Delay =>" , delay);
77 | setTimeout(() => sendContestReminder(contest), delay);
78 | console.log(`Scheduled reminder for ${contest.title} at ${reminderTime}`);
79 | }
80 | });
81 | }
82 |
83 |
84 | // Function to send contest reminder
85 | async function sendContestReminder(contest) {
86 |
87 |
88 | const embed = new EmbedBuilder()
89 | .setTitle(contest.title)
90 | .setURL(contest.href)
91 | .setColor(0xfea116)
92 | .addFields(
93 | { name: 'Start Time', value: contest.start_time.toUTCString(), inline: true },
94 | { name: 'Duration', value: `${contest.duration / 60} minutes`, inline: true },
95 | { name: 'Link', value: `[Join Contest](${contest.href})` }
96 | )
97 | .setFooter({ text: 'Contest starts in 1 hour!' });
98 |
99 |
100 | for (const guild of client.guilds.cache.values()) {
101 | for (const channel of guild.channels.cache.values()) {
102 | // Type 0 => Text Channel
103 | if (channel.type === 0) {
104 | await channel.send({
105 | content: bold(`@everyone, Upcoming LeetCode Contest Reminder!`),
106 | embeds: [embed]
107 | });
108 | break;
109 | }
110 | }
111 | }
112 |
113 | }
114 |
115 |
116 |
117 | // Refresh contest schedule every Friday
118 | cron.schedule('0 0 * * 5', scheduleContestReminders, {
119 | scheduled: true,
120 | timezone: "UTC"
121 | });
122 | }
123 |
--------------------------------------------------------------------------------
/example.env:
--------------------------------------------------------------------------------
1 | TOKEN=
2 | CHANNEL_ID=
3 | DEVELOPER_ID=
--------------------------------------------------------------------------------
/interactions/buttons/topic.js:
--------------------------------------------------------------------------------
1 | import { EmbedBuilder } from "discord.js";
2 |
3 | const topics = [
4 | 'Array', 'String', 'Hash Table', 'Dynamic Programming', 'Math',
5 | 'Sorting', 'Greedy', 'Depth-First Search', 'Binary Search', 'Database',
6 | 'Breadth-First Search', 'Tree', 'Matrix', 'Two Pointers', 'Bit Manipulation',
7 | 'Stack', 'Design', 'Heap (Priority Queue)', 'Graph', 'Simulation'
8 | ];
9 |
10 | export default {
11 | name: 'topic',
12 | run: async (interaction, lc = interaction.client.lc) => {
13 | await interaction.deferReply();
14 |
15 | const [,topicIndex,difficultyIndex] = interaction.customId.split("_")
16 |
17 | const difficulty = difficultyIndex === '0' ? null : difficultyIndex === '1' ? 'EASY' : difficultyIndex === '2' ? 'MEDIUM' : 'HARD'
18 | const topic = topics[topicIndex]
19 |
20 |
21 | const topicQuestions = await lc.problems({
22 | categorySlug: '',
23 | skip: 0,
24 | limit: 300000,
25 | filters: { tags: [topic.toLowerCase().replace(/\s+/g, '-')], difficulty }
26 | });
27 |
28 | if (topicQuestions.questions.length === 0) {
29 | await interaction.editReply('No questions found for this topic.');
30 | return;
31 | }
32 |
33 | const randomQuestion = topicQuestions.questions[Math.floor(Math.random() * topicQuestions.questions.length)];
34 |
35 | const questionLink = `https://leetcode.com/problems/${randomQuestion.titleSlug}/`;
36 | const embed = new EmbedBuilder()
37 | .setTitle(`Random ${topic} Question: ${randomQuestion.title}`)
38 | .setURL(questionLink)
39 | .setColor(0x0099FF)
40 | .addFields(
41 | { name: 'Difficulty', value: randomQuestion.difficulty, inline: true },
42 | { name: 'Link', value: `[Solve Problem](${questionLink})`, inline: true },
43 | { name: 'Acceptance Rate', value: `${randomQuestion.acRate.toFixed(2)}%`, inline: true }
44 | )
45 | .setFooter({ text: 'Good luck solving this problem!' });
46 |
47 | await interaction.editReply({ embeds: [embed], components: [] });
48 | }
49 | }
--------------------------------------------------------------------------------
/interactions/commands/contests.js:
--------------------------------------------------------------------------------
1 | import { SlashCommandBuilder, EmbedBuilder } from "discord.js";
2 | import { createRequire } from 'module';
3 | const require = createRequire(import.meta.url);
4 | const https = require('https');
5 |
6 | function formatTimeLeft(contestTime) {
7 | const now = new Date();
8 | const contestDate = new Date(contestTime);
9 | const timeDiff = contestDate - now;
10 |
11 | if (timeDiff < 0) return "Contest has started";
12 |
13 | const days = Math.floor(timeDiff / (1000 * 60 * 60 * 24));
14 | const hours = Math.floor((timeDiff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
15 | const minutes = Math.floor((timeDiff % (1000 * 60 * 60)) / (1000 * 60));
16 |
17 | let timeString = "";
18 | if (days > 0) timeString += `${days}d `;
19 | if (hours > 0) timeString += `${hours}h `;
20 | if (minutes > 0) timeString += `${minutes}m`;
21 |
22 | return timeString.trim() || "Less than a minute";
23 | }
24 |
25 | // Helper function to make an HTTPS GET request
26 | function fetchData(url) {
27 | return new Promise((resolve, reject) => {
28 | https.get(url, (response) => {
29 | let data = '';
30 |
31 | // A chunk of data has been received
32 | response.on('data', (chunk) => {
33 | data += chunk;
34 | });
35 |
36 | response.on('end', () => {
37 | // console.log("Raw response data:", data);
38 |
39 | try {
40 | // Check if the content type is JSON or text
41 | const contentType = response.headers['content-type'];
42 | if (contentType && contentType.includes('application/json')) {
43 | // If it's JSON, parse it directly
44 | const parsedData = JSON.parse(data);
45 | resolve(parsedData);
46 | } else if (contentType && contentType.includes('text/plain')) {
47 | // If it's plain text, attempt to parse as JSON-like string
48 | const parsedData = JSON.parse(data);
49 | resolve(parsedData);
50 | } else {
51 | console.error("Unexpected content type:", contentType);
52 | reject(new Error('Response is not JSON or text'));
53 | }
54 | } catch (error) {
55 | console.error('Error parsing data:', error);
56 | reject(new Error('Error parsing data'));
57 | }
58 | });
59 | }).on('error', (error) => {
60 | reject(error);
61 | });
62 | });
63 | }
64 |
65 |
66 | export default {
67 | data: new SlashCommandBuilder()
68 | .setName('contests')
69 | .setDescription('Shows upcoming LeetCode contests'),
70 | run: async (interaction) => {
71 | await interaction.deferReply();
72 |
73 | try {
74 | const url = 'https://contest-hive.vercel.app/api/leetcode'; // Your HTTP URL
75 | const response = await fetchData(url);
76 | const contests = response.data;
77 |
78 | if (!contests || contests.length === 0) {
79 | return interaction.editReply('No upcoming contests found.');
80 | }
81 |
82 | const embed = new EmbedBuilder()
83 | .setTitle('Upcoming LeetCode Contests')
84 | .setColor(0xD1006C)
85 | .setDescription('Here are the upcoming LeetCode contests:')
86 | .setTimestamp();
87 |
88 | contests.forEach((contest, index) => {
89 | // Skip if contest has ended
90 | if (new Date(contest.startTime) < new Date()) return;
91 |
92 | // Clean up the URL by removing 'undefined'
93 | let cleanedUrl = contest.url ? contest.url.replace('undefined', '') : '';
94 |
95 | // Format contest duration to hours and minutes
96 | const duration = contest.duration / 60;
97 | const hours = Math.floor(duration / 60);
98 | const minutes = duration % 60;
99 | const durationStr = `${hours}h ${minutes}m`;
100 |
101 | embed.addFields({
102 | name: `${index + 1}. ${contest.title}`,
103 | value: `
104 | **Starts In:** ${formatTimeLeft(contest.startTime)}
105 | **Duration:** ${durationStr}
106 | **Date:**
107 | **Link:** [Contest Link](https://leetcode.com/contest/${cleanedUrl})
108 |
109 | `,
110 | inline: false
111 | });
112 | });
113 |
114 | embed.setFooter({
115 | text: 'Pro Tip: Don\'t forget to register for the contests!'
116 | });
117 |
118 | return interaction.editReply({ embeds: [embed] });
119 |
120 | } catch (error) {
121 | console.error('Error fetching contests:', error);
122 | return interaction.editReply('Sorry, there was an error fetching the contest information. Please try again later.');
123 | }
124 | }
125 | };
126 |
--------------------------------------------------------------------------------
/interactions/commands/help.js:
--------------------------------------------------------------------------------
1 | import { SlashCommandBuilder, EmbedBuilder, CommandInteraction} from "discord.js";
2 |
3 | export default {
4 | data: new SlashCommandBuilder()
5 | .setName('help')
6 | .setDescription('Get the list of commands available for use'),
7 | /**
8 | *
9 | * @param {CommandInteraction} interaction
10 | * @returns
11 | */
12 | run: async (interaction, client=interaction.client) => {
13 | const embed = new EmbedBuilder()
14 | .setDescription(`Hey __${interaction.user.displayName}__ 👋, I am a discord bot which integrates with LeetCode to provide daily challenges, random problems, user information, and more.\n`)
15 | .setColor(0xD1006C)
16 | .addFields(
17 | ...client.commands.map(command => ({ name: `> /${command.data.name}`, value: `${command.data.description}`, inline: true })),
18 | { name: '', value: 'Please consider giving me a ⭐ on GitHub ([DDoL](https://github.com/jinx-vi-0/DDoL)) to show your support!'}
19 | )
20 | return interaction.reply({ embeds: [ embed ] });
21 | }
22 | }
--------------------------------------------------------------------------------
/interactions/commands/potd.js:
--------------------------------------------------------------------------------
1 | import { SlashCommandBuilder, EmbedBuilder } from "discord.js";
2 |
3 | export default {
4 | data: new SlashCommandBuilder()
5 | .setName('potd')
6 | .setDescription('Shows the LeetCode Daily Challenge'),
7 | run: async (interaction, lc=interaction.client.lc) => {
8 | const daily = await lc.daily();
9 | const questionLink = `https://leetcode.com${daily.link}`;
10 |
11 | const embed = new EmbedBuilder()
12 | .setTitle(`LeetCode Daily Challenge - ${daily.date}`)
13 | .setURL(questionLink)
14 | .setDescription(`**${daily.question.title}**`)
15 | .setColor(0xD1006C)
16 | .addFields(
17 | { name: 'Difficulty', value: daily.question.difficulty, inline: true },
18 | { name: 'Link', value: `[Click here](${questionLink})`, inline: true }
19 | )
20 | .setFooter({ text: 'Good luck solving today\'s problem!' });
21 |
22 | return interaction.reply({ embeds: [embed] });
23 | }
24 | }
--------------------------------------------------------------------------------
/interactions/commands/random.js:
--------------------------------------------------------------------------------
1 | import { SlashCommandBuilder, EmbedBuilder } from "discord.js";
2 | import LeetCodeUtility from "../../utility/LeetCode.js";
3 | import fs from 'fs'
4 |
5 | export default {
6 | data: new SlashCommandBuilder()
7 | .setName('random')
8 | .setDescription('Shows the LeetCode Daily Challenge')
9 | .addStringOption((option) => option
10 | .setName('difficulty')
11 | .setDescription('Mention how hard you want problem to be')
12 | .addChoices({ name: 'easy', value: '1' }, { name: 'medium', value: '2' }, { name: 'hard', value: '3' })
13 | .setRequired(true)
14 | ),
15 | run: async (interaction) => {
16 | await interaction.deferReply()
17 | const difficulty = parseInt(interaction.options.getString('difficulty'));
18 |
19 | let problems = JSON.parse(fs.readFileSync('./cache/problems.json'))
20 |
21 | if (!problems.length) { /** fetch problems and save them */
22 | problems = await LeetCodeUtility.fetchLeetCodeProblems();
23 | fs.writeFileSync('./cache/problems.json', JSON.stringify(problems))
24 | }
25 |
26 | let filteredProblems = problems.filter(problem => problem.difficulty.level === difficulty);
27 |
28 | if (filteredProblems.length === 0) {
29 | return await interaction
30 | .followUp(`Sorry, I couldn't find any LeetCode problems with the given difficulty level`);
31 | }
32 |
33 | const randomIndex = Math.floor(Math.random() * filteredProblems.length);
34 | const problem = filteredProblems[randomIndex].stat;
35 | const questionLink = `https://leetcode.com/problems/${problem.question__title_slug}/`;
36 |
37 | const embedColor = difficulty == 1 ? 0x00FF00 : difficulty == 2 ? 0xFFFF00 : 0xFF0000
38 |
39 | const embed = new EmbedBuilder()
40 | .setTitle(`${problem.question__title}`)
41 | .setURL(questionLink)
42 | .setColor(embedColor)
43 | .addFields(
44 | { name: 'Difficulty', value: difficulty === 1 ? 'Easy' : difficulty === 2 ? 'Medium' : 'Hard', inline: true },
45 | { name: 'Link', value: `[Solve Problem](${questionLink})`, inline: true },
46 | { name: 'Acceptance Rate', value: `${problem.total_acs} / ${problem.total_submitted} (${(problem.total_acs / problem.total_submitted * 100).toFixed(2)}%)`, inline: true }
47 | )
48 | .setFooter({ text: 'Good luck!' });
49 |
50 |
51 | return interaction.followUp({ embeds: [ embed ]})
52 | }
53 | }
--------------------------------------------------------------------------------
/interactions/commands/streak.js:
--------------------------------------------------------------------------------
1 | import { SlashCommandBuilder } from "discord.js";
2 | import LeetCodeUtility from "../../utility/LeetCode.js";
3 |
4 |
5 | export default {
6 | data: new SlashCommandBuilder()
7 | .setName('streak')
8 | .setDescription('Shows user Streak Info')
9 | .addStringOption(
10 | (option) => option
11 | .setName('username')
12 | .setDescription('Unique username of user')
13 | .setRequired(true)
14 | ),
15 | run: async (interaction, lc=interaction.client.lc) => {
16 | await interaction.deferReply()
17 |
18 | const username = interaction.options.getString('username')
19 | const user = await lc.user(username);
20 |
21 | let streakInfo = 0;
22 | let hasSolvedToday = false;
23 |
24 | if (user.matchedUser) {
25 | ({ currentStreak: streakInfo, hasSolvedToday } = LeetCodeUtility.calculateStreak(user.matchedUser.submissionCalendar));
26 | }
27 |
28 | let streakMessage;
29 | if (streakInfo > 0) {
30 | if (hasSolvedToday) {
31 | streakMessage = `🎉 **${username}** has solved a problem for ${streakInfo} consecutive days! Great work, keep it up! 💪`;
32 | } else {
33 | streakMessage = `⚠️ **${username}** has solved a problem for ${streakInfo} consecutive days! Solve today's problem to maintain your streak and prevent it from resetting! 🔄`;
34 | }
35 | } else {
36 | streakMessage = `❌ **${username}** does not have a streak yet. Start solving problems today to build your streak! 🚀`;
37 | }
38 |
39 | return interaction.followUp(streakMessage);
40 |
41 | }
42 | }
--------------------------------------------------------------------------------
/interactions/commands/topics.js:
--------------------------------------------------------------------------------
1 | import { SlashCommandBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder } from "discord.js";
2 | import LeetCodeUtility from "../../utility/LeetCode.js";
3 |
4 | const topics = [
5 | 'Array', 'String', 'Hash Table', 'Dynamic Programming', 'Math',
6 | 'Sorting', 'Greedy', 'Depth-First Search', 'Binary Search', 'Database',
7 | 'Breadth-First Search', 'Tree', 'Matrix', 'Two Pointers', 'Bit Manipulation',
8 | 'Stack', 'Design', 'Heap (Priority Queue)', 'Graph', 'Simulation'
9 | ];
10 |
11 | export default {
12 | data: new SlashCommandBuilder()
13 | .setName('topics')
14 | .setDescription('Shows a list of LeetCode topics to choose from')
15 | .addStringOption(
16 | (option) => option
17 | .setName('difficulty')
18 | .setDescription('Difficulty of the random question found based on topic')
19 | .addChoices(
20 | { name: 'random', value: '0' },
21 | { name: 'easy', value: '1' },
22 | { name: 'medium', value: '2' },
23 | { name: 'hard', value: '3' }
24 | )
25 | .setRequired(false)
26 | ),
27 | run: async (interaction) => {
28 | const difficulty = interaction.options.getString('difficulty') || '0'
29 | const chunkedTopics = LeetCodeUtility.chunkArray(topics, 5);
30 |
31 | const rows = chunkedTopics.map(chunk =>
32 | new ActionRowBuilder().addComponents(
33 | chunk.map((topic) =>
34 | new ButtonBuilder()
35 | .setCustomId(`topic_${topics.indexOf(topic)}_${difficulty}`)
36 | .setLabel(topic)
37 | .setStyle(ButtonStyle.Secondary)
38 | )
39 | )
40 | );
41 |
42 | const embed = new EmbedBuilder()
43 | .setDescription('**Choose a topic to get a random question**')
44 | .setColor(0xfea116)
45 |
46 | return interaction.reply({ embeds: [ embed ], components: rows })
47 | }
48 | }
--------------------------------------------------------------------------------
/interactions/commands/user.js:
--------------------------------------------------------------------------------
1 | import { SlashCommandBuilder, EmbedBuilder } from "discord.js";
2 |
3 | export default {
4 | data: new SlashCommandBuilder()
5 | .setName('user')
6 | .setDescription(' Shows user Info')
7 | .addStringOption(
8 | (option) => option
9 | .setName('username')
10 | .setDescription('Unique username of user')
11 | .setRequired(true)
12 | ),
13 | run: async (interaction, lc=interaction.client.lc) => {
14 | await interaction.deferReply()
15 | const username = interaction.options.getString('username')
16 |
17 | const [userInfo, contestInfo] = await Promise.all([
18 | lc.user(username),
19 | lc.user_contest_info(username)
20 | ]);
21 |
22 | if (!userInfo.matchedUser) {
23 | return interaction.followUp({ content: `User "${username}" not found.`});
24 | }
25 |
26 | const user = userInfo.matchedUser;
27 | const profile = user.profile;
28 | const submitStats = user.submitStats;
29 |
30 | const embed = new EmbedBuilder()
31 | .setColor('#FFD700') // Gold color for the embed
32 | .setTitle(`LeetCode Profile: **${username}**`)
33 | .setThumbnail(profile.userAvatar)
34 | .addFields(
35 | { name: '👤 Real Name', value: profile.realName || '*Not provided*', inline: true },
36 | { name: '🏆 Ranking', value: profile.ranking ? profile.ranking.toString() : '*Not ranked*', inline: true },
37 | { name: '🌍 Country', value: profile.countryName || '*Not provided*', inline: true },
38 | { name: '🏢 Company', value: profile.company || '*Not provided*', inline: true },
39 | { name: '🎓 School', value: profile.school || '*Not provided*', inline: true },
40 | { name: '\u200B', value: '⬇️ **Problem Solving Stats**', inline: false },
41 | { name: '🟢 Easy', value: `Solved: ${submitStats.acSubmissionNum[1].count} / ${submitStats.totalSubmissionNum[1].count}`, inline: true },
42 | { name: '🟠 Medium', value: `Solved: ${submitStats.acSubmissionNum[2].count} / ${submitStats.totalSubmissionNum[2].count}`, inline: true },
43 | { name: '🔴 Hard', value: `Solved: ${submitStats.acSubmissionNum[3].count} / ${submitStats.totalSubmissionNum[3].count}`, inline: true },
44 | { name: '📊 Total', value: `Solved: ${submitStats.acSubmissionNum[0].count} / ${submitStats.totalSubmissionNum[0].count}`, inline: true }
45 | );
46 |
47 | if (contestInfo.userContestRanking) {
48 | embed.addFields(
49 | { name: '🚩 **Contest Info**', value: `\`\`\`Rating: ${Math.round(contestInfo.userContestRanking.rating)}\nRanking: ${contestInfo.userContestRanking.globalRanking}\nTop: ${contestInfo.userContestRanking.topPercentage.toFixed(2)}%\nAttended: ${contestInfo.userContestRanking.attendedContestsCount}\`\`\`` }
50 | );
51 | }
52 |
53 | if (user.badges && user.badges.length > 0) {
54 | const badgeNames = user.badges.map(badge => badge.displayName).join('\n•');
55 | embed.addFields({ name: '🏅 Badges', value: "•" + badgeNames, inline: false });
56 | }
57 |
58 | return interaction.followUp({ embeds: [ embed ]})
59 | }
60 | }
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bot",
3 | "version": "1.0.0",
4 | "lockfileVersion": 3,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "bot",
9 | "version": "1.0.0",
10 | "license": "ISC",
11 | "dependencies": {
12 | "axios": "^1.7.7",
13 | "discord.js": "^14.15.3",
14 | "dotenv": "^16.4.5",
15 | "leetcode-query": "^1.2.3",
16 | "node-cron": "^3.0.3"
17 | }
18 | },
19 | "node_modules/@discordjs/builders": {
20 | "version": "1.8.2",
21 | "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.8.2.tgz",
22 | "integrity": "sha512-6wvG3QaCjtMu0xnle4SoOIeFB4y6fKMN6WZfy3BMKJdQQtPLik8KGzDwBVL/+wTtcE/ZlFjgEk74GublyEVZ7g==",
23 | "dependencies": {
24 | "@discordjs/formatters": "^0.4.0",
25 | "@discordjs/util": "^1.1.0",
26 | "@sapphire/shapeshift": "^3.9.7",
27 | "discord-api-types": "0.37.83",
28 | "fast-deep-equal": "^3.1.3",
29 | "ts-mixer": "^6.0.4",
30 | "tslib": "^2.6.2"
31 | },
32 | "engines": {
33 | "node": ">=16.11.0"
34 | },
35 | "funding": {
36 | "url": "https://github.com/discordjs/discord.js?sponsor"
37 | }
38 | },
39 | "node_modules/@discordjs/collection": {
40 | "version": "1.5.3",
41 | "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-1.5.3.tgz",
42 | "integrity": "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ==",
43 | "engines": {
44 | "node": ">=16.11.0"
45 | }
46 | },
47 | "node_modules/@discordjs/formatters": {
48 | "version": "0.4.0",
49 | "resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.4.0.tgz",
50 | "integrity": "sha512-fJ06TLC1NiruF35470q3Nr1bi95BdvKFAF+T5bNfZJ4bNdqZ3VZ+Ttg6SThqTxm6qumSG3choxLBHMC69WXNXQ==",
51 | "dependencies": {
52 | "discord-api-types": "0.37.83"
53 | },
54 | "engines": {
55 | "node": ">=16.11.0"
56 | },
57 | "funding": {
58 | "url": "https://github.com/discordjs/discord.js?sponsor"
59 | }
60 | },
61 | "node_modules/@discordjs/rest": {
62 | "version": "2.3.0",
63 | "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.3.0.tgz",
64 | "integrity": "sha512-C1kAJK8aSYRv3ZwMG8cvrrW4GN0g5eMdP8AuN8ODH5DyOCbHgJspze1my3xHOAgwLJdKUbWNVyAeJ9cEdduqIg==",
65 | "dependencies": {
66 | "@discordjs/collection": "^2.1.0",
67 | "@discordjs/util": "^1.1.0",
68 | "@sapphire/async-queue": "^1.5.2",
69 | "@sapphire/snowflake": "^3.5.3",
70 | "@vladfrangu/async_event_emitter": "^2.2.4",
71 | "discord-api-types": "0.37.83",
72 | "magic-bytes.js": "^1.10.0",
73 | "tslib": "^2.6.2",
74 | "undici": "6.13.0"
75 | },
76 | "engines": {
77 | "node": ">=16.11.0"
78 | },
79 | "funding": {
80 | "url": "https://github.com/discordjs/discord.js?sponsor"
81 | }
82 | },
83 | "node_modules/@discordjs/rest/node_modules/@discordjs/collection": {
84 | "version": "2.1.0",
85 | "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.0.tgz",
86 | "integrity": "sha512-mLcTACtXUuVgutoznkh6hS3UFqYirDYAg5Dc1m8xn6OvPjetnUlf/xjtqnnc47OwWdaoCQnHmHh9KofhD6uRqw==",
87 | "engines": {
88 | "node": ">=18"
89 | },
90 | "funding": {
91 | "url": "https://github.com/discordjs/discord.js?sponsor"
92 | }
93 | },
94 | "node_modules/@discordjs/util": {
95 | "version": "1.1.0",
96 | "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.1.0.tgz",
97 | "integrity": "sha512-IndcI5hzlNZ7GS96RV3Xw1R2kaDuXEp7tRIy/KlhidpN/BQ1qh1NZt3377dMLTa44xDUNKT7hnXkA/oUAzD/lg==",
98 | "engines": {
99 | "node": ">=16.11.0"
100 | },
101 | "funding": {
102 | "url": "https://github.com/discordjs/discord.js?sponsor"
103 | }
104 | },
105 | "node_modules/@discordjs/ws": {
106 | "version": "1.1.1",
107 | "resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.1.1.tgz",
108 | "integrity": "sha512-PZ+vLpxGCRtmr2RMkqh8Zp+BenUaJqlS6xhgWKEZcgC/vfHLEzpHtKkB0sl3nZWpwtcKk6YWy+pU3okL2I97FA==",
109 | "dependencies": {
110 | "@discordjs/collection": "^2.1.0",
111 | "@discordjs/rest": "^2.3.0",
112 | "@discordjs/util": "^1.1.0",
113 | "@sapphire/async-queue": "^1.5.2",
114 | "@types/ws": "^8.5.10",
115 | "@vladfrangu/async_event_emitter": "^2.2.4",
116 | "discord-api-types": "0.37.83",
117 | "tslib": "^2.6.2",
118 | "ws": "^8.16.0"
119 | },
120 | "engines": {
121 | "node": ">=16.11.0"
122 | },
123 | "funding": {
124 | "url": "https://github.com/discordjs/discord.js?sponsor"
125 | }
126 | },
127 | "node_modules/@discordjs/ws/node_modules/@discordjs/collection": {
128 | "version": "2.1.0",
129 | "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.0.tgz",
130 | "integrity": "sha512-mLcTACtXUuVgutoznkh6hS3UFqYirDYAg5Dc1m8xn6OvPjetnUlf/xjtqnnc47OwWdaoCQnHmHh9KofhD6uRqw==",
131 | "engines": {
132 | "node": ">=18"
133 | },
134 | "funding": {
135 | "url": "https://github.com/discordjs/discord.js?sponsor"
136 | }
137 | },
138 | "node_modules/@fetch-impl/cross-fetch": {
139 | "version": "1.0.0",
140 | "resolved": "https://registry.npmjs.org/@fetch-impl/cross-fetch/-/cross-fetch-1.0.0.tgz",
141 | "integrity": "sha512-vNvwtCQ7yruvpYnp1i/4paVi/icrGYx9O4eHNDYorjTAFg78bhitO0l39opJSVfsTKqyWqj3+2+YenoGhZOCJA==",
142 | "peerDependencies": {
143 | "@fetch-impl/fetcher": "^1.0.0",
144 | "cross-fetch": "*"
145 | }
146 | },
147 | "node_modules/@fetch-impl/fetcher": {
148 | "version": "1.0.0",
149 | "resolved": "https://registry.npmjs.org/@fetch-impl/fetcher/-/fetcher-1.0.0.tgz",
150 | "integrity": "sha512-UPUN9Yfjnk513Vc08iNW8/9L1nSwQMsTx6nOvmjPNfU2Rtbew/2KgAbQDPuoL6PrNgEmEmmyeM29BkcVBpt3gQ=="
151 | },
152 | "node_modules/@sapphire/async-queue": {
153 | "version": "1.5.2",
154 | "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.2.tgz",
155 | "integrity": "sha512-7X7FFAA4DngXUl95+hYbUF19bp1LGiffjJtu7ygrZrbdCSsdDDBaSjB7Akw0ZbOu6k0xpXyljnJ6/RZUvLfRdg==",
156 | "engines": {
157 | "node": ">=v14.0.0",
158 | "npm": ">=7.0.0"
159 | }
160 | },
161 | "node_modules/@sapphire/shapeshift": {
162 | "version": "3.9.7",
163 | "resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-3.9.7.tgz",
164 | "integrity": "sha512-4It2mxPSr4OGn4HSQWGmhFMsNFGfFVhWeRPCRwbH972Ek2pzfGRZtb0pJ4Ze6oIzcyh2jw7nUDa6qGlWofgd9g==",
165 | "dependencies": {
166 | "fast-deep-equal": "^3.1.3",
167 | "lodash": "^4.17.21"
168 | },
169 | "engines": {
170 | "node": ">=v16"
171 | }
172 | },
173 | "node_modules/@sapphire/snowflake": {
174 | "version": "3.5.3",
175 | "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.3.tgz",
176 | "integrity": "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ==",
177 | "engines": {
178 | "node": ">=v14.0.0",
179 | "npm": ">=7.0.0"
180 | }
181 | },
182 | "node_modules/@types/node": {
183 | "version": "20.14.10",
184 | "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.10.tgz",
185 | "integrity": "sha512-MdiXf+nDuMvY0gJKxyfZ7/6UFsETO7mGKF54MVD/ekJS6HdFtpZFBgrh6Pseu64XTb2MLyFPlbW6hj8HYRQNOQ==",
186 | "dependencies": {
187 | "undici-types": "~5.26.4"
188 | }
189 | },
190 | "node_modules/@types/ws": {
191 | "version": "8.5.10",
192 | "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz",
193 | "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==",
194 | "dependencies": {
195 | "@types/node": "*"
196 | }
197 | },
198 | "node_modules/@vladfrangu/async_event_emitter": {
199 | "version": "2.4.1",
200 | "resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.4.1.tgz",
201 | "integrity": "sha512-cedU1DrzO4oliUigSAOqSgts6wEfGGSbpO1hYxvKbz8sr7a0meyP3GxnL6hIUtBK0nMG6zHfIYWcqOIb+MRI7w==",
202 | "engines": {
203 | "node": ">=v14.0.0",
204 | "npm": ">=7.0.0"
205 | }
206 | },
207 | "node_modules/asynckit": {
208 | "version": "0.4.0",
209 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
210 | "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
211 | "license": "MIT"
212 | },
213 | "node_modules/axios": {
214 | "version": "1.7.7",
215 | "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz",
216 | "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==",
217 | "license": "MIT",
218 | "dependencies": {
219 | "follow-redirects": "^1.15.6",
220 | "form-data": "^4.0.0",
221 | "proxy-from-env": "^1.1.0"
222 | }
223 | },
224 | "node_modules/combined-stream": {
225 | "version": "1.0.8",
226 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
227 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
228 | "license": "MIT",
229 | "dependencies": {
230 | "delayed-stream": "~1.0.0"
231 | },
232 | "engines": {
233 | "node": ">= 0.8"
234 | }
235 | },
236 | "node_modules/cross-fetch": {
237 | "version": "4.0.0",
238 | "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz",
239 | "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==",
240 | "dependencies": {
241 | "node-fetch": "^2.6.12"
242 | }
243 | },
244 | "node_modules/delayed-stream": {
245 | "version": "1.0.0",
246 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
247 | "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
248 | "license": "MIT",
249 | "engines": {
250 | "node": ">=0.4.0"
251 | }
252 | },
253 | "node_modules/discord-api-types": {
254 | "version": "0.37.83",
255 | "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.83.tgz",
256 | "integrity": "sha512-urGGYeWtWNYMKnYlZnOnDHm8fVRffQs3U0SpE8RHeiuLKb/u92APS8HoQnPTFbnXmY1vVnXjXO4dOxcAn3J+DA=="
257 | },
258 | "node_modules/discord.js": {
259 | "version": "14.15.3",
260 | "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.15.3.tgz",
261 | "integrity": "sha512-/UJDQO10VuU6wQPglA4kz2bw2ngeeSbogiIPx/TsnctfzV/tNf+q+i1HlgtX1OGpeOBpJH9erZQNO5oRM2uAtQ==",
262 | "dependencies": {
263 | "@discordjs/builders": "^1.8.2",
264 | "@discordjs/collection": "1.5.3",
265 | "@discordjs/formatters": "^0.4.0",
266 | "@discordjs/rest": "^2.3.0",
267 | "@discordjs/util": "^1.1.0",
268 | "@discordjs/ws": "^1.1.1",
269 | "@sapphire/snowflake": "3.5.3",
270 | "discord-api-types": "0.37.83",
271 | "fast-deep-equal": "3.1.3",
272 | "lodash.snakecase": "4.1.1",
273 | "tslib": "2.6.2",
274 | "undici": "6.13.0"
275 | },
276 | "engines": {
277 | "node": ">=16.11.0"
278 | },
279 | "funding": {
280 | "url": "https://github.com/discordjs/discord.js?sponsor"
281 | }
282 | },
283 | "node_modules/dotenv": {
284 | "version": "16.4.5",
285 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz",
286 | "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==",
287 | "engines": {
288 | "node": ">=12"
289 | },
290 | "funding": {
291 | "url": "https://dotenvx.com"
292 | }
293 | },
294 | "node_modules/eventemitter3": {
295 | "version": "5.0.1",
296 | "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
297 | "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="
298 | },
299 | "node_modules/fast-deep-equal": {
300 | "version": "3.1.3",
301 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
302 | "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
303 | },
304 | "node_modules/follow-redirects": {
305 | "version": "1.15.9",
306 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
307 | "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
308 | "funding": [
309 | {
310 | "type": "individual",
311 | "url": "https://github.com/sponsors/RubenVerborgh"
312 | }
313 | ],
314 | "license": "MIT",
315 | "engines": {
316 | "node": ">=4.0"
317 | },
318 | "peerDependenciesMeta": {
319 | "debug": {
320 | "optional": true
321 | }
322 | }
323 | },
324 | "node_modules/form-data": {
325 | "version": "4.0.0",
326 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
327 | "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
328 | "license": "MIT",
329 | "dependencies": {
330 | "asynckit": "^0.4.0",
331 | "combined-stream": "^1.0.8",
332 | "mime-types": "^2.1.12"
333 | },
334 | "engines": {
335 | "node": ">= 6"
336 | }
337 | },
338 | "node_modules/leetcode-query": {
339 | "version": "1.2.3",
340 | "resolved": "https://registry.npmjs.org/leetcode-query/-/leetcode-query-1.2.3.tgz",
341 | "integrity": "sha512-J73ehmjgIfDd3Ugg1ee83bDALNaGh78nQwDS5qyPOsgGahK55a2lcl9Q/iaByKi6AUMMxsHEyEsUNGBX4Lmn2Q==",
342 | "dependencies": {
343 | "@fetch-impl/cross-fetch": "^1.0.0",
344 | "@fetch-impl/fetcher": "^1.0.0",
345 | "cross-fetch": "^4.0.0",
346 | "eventemitter3": "^5.0.1"
347 | }
348 | },
349 | "node_modules/lodash": {
350 | "version": "4.17.21",
351 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
352 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
353 | },
354 | "node_modules/lodash.snakecase": {
355 | "version": "4.1.1",
356 | "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz",
357 | "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw=="
358 | },
359 | "node_modules/magic-bytes.js": {
360 | "version": "1.10.0",
361 | "resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.10.0.tgz",
362 | "integrity": "sha512-/k20Lg2q8LE5xiaaSkMXk4sfvI+9EGEykFS4b0CHHGWqDYU0bGUFSwchNOMA56D7TCs9GwVTkqe9als1/ns8UQ=="
363 | },
364 | "node_modules/mime-db": {
365 | "version": "1.52.0",
366 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
367 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
368 | "license": "MIT",
369 | "engines": {
370 | "node": ">= 0.6"
371 | }
372 | },
373 | "node_modules/mime-types": {
374 | "version": "2.1.35",
375 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
376 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
377 | "license": "MIT",
378 | "dependencies": {
379 | "mime-db": "1.52.0"
380 | },
381 | "engines": {
382 | "node": ">= 0.6"
383 | }
384 | },
385 | "node_modules/node-cron": {
386 | "version": "3.0.3",
387 | "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.3.tgz",
388 | "integrity": "sha512-dOal67//nohNgYWb+nWmg5dkFdIwDm8EpeGYMekPMrngV3637lqnX0lbUcCtgibHTz6SEz7DAIjKvKDFYCnO1A==",
389 | "dependencies": {
390 | "uuid": "8.3.2"
391 | },
392 | "engines": {
393 | "node": ">=6.0.0"
394 | }
395 | },
396 | "node_modules/node-fetch": {
397 | "version": "2.7.0",
398 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
399 | "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
400 | "dependencies": {
401 | "whatwg-url": "^5.0.0"
402 | },
403 | "engines": {
404 | "node": "4.x || >=6.0.0"
405 | },
406 | "peerDependencies": {
407 | "encoding": "^0.1.0"
408 | },
409 | "peerDependenciesMeta": {
410 | "encoding": {
411 | "optional": true
412 | }
413 | }
414 | },
415 | "node_modules/proxy-from-env": {
416 | "version": "1.1.0",
417 | "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
418 | "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
419 | "license": "MIT"
420 | },
421 | "node_modules/tr46": {
422 | "version": "0.0.3",
423 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
424 | "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
425 | },
426 | "node_modules/ts-mixer": {
427 | "version": "6.0.4",
428 | "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz",
429 | "integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA=="
430 | },
431 | "node_modules/tslib": {
432 | "version": "2.6.2",
433 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
434 | "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
435 | },
436 | "node_modules/undici": {
437 | "version": "6.13.0",
438 | "resolved": "https://registry.npmjs.org/undici/-/undici-6.13.0.tgz",
439 | "integrity": "sha512-Q2rtqmZWrbP8nePMq7mOJIN98M0fYvSgV89vwl/BQRT4mDOeY2GXZngfGpcBBhtky3woM7G24wZV3Q304Bv6cw==",
440 | "engines": {
441 | "node": ">=18.0"
442 | }
443 | },
444 | "node_modules/undici-types": {
445 | "version": "5.26.5",
446 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
447 | "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
448 | },
449 | "node_modules/uuid": {
450 | "version": "8.3.2",
451 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
452 | "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
453 | "bin": {
454 | "uuid": "dist/bin/uuid"
455 | }
456 | },
457 | "node_modules/webidl-conversions": {
458 | "version": "3.0.1",
459 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
460 | "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
461 | },
462 | "node_modules/whatwg-url": {
463 | "version": "5.0.0",
464 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
465 | "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
466 | "dependencies": {
467 | "tr46": "~0.0.3",
468 | "webidl-conversions": "^3.0.0"
469 | }
470 | },
471 | "node_modules/ws": {
472 | "version": "8.18.0",
473 | "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
474 | "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
475 | "engines": {
476 | "node": ">=10.0.0"
477 | },
478 | "peerDependencies": {
479 | "bufferutil": "^4.0.1",
480 | "utf-8-validate": ">=5.0.2"
481 | },
482 | "peerDependenciesMeta": {
483 | "bufferutil": {
484 | "optional": true
485 | },
486 | "utf-8-validate": {
487 | "optional": true
488 | }
489 | }
490 | }
491 | }
492 | }
493 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bot",
3 | "version": "1.0.0",
4 | "type": "module",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "node bot.js",
8 | "test": "echo \"Error: no test specified\" && exit 1"
9 | },
10 | "keywords": [],
11 | "author": "",
12 | "license": "ISC",
13 | "description": "",
14 | "dependencies": {
15 | "axios": "^1.7.7",
16 | "discord.js": "^14.15.3",
17 | "dotenv": "^16.4.5",
18 | "leetcode-query": "^1.2.3",
19 | "node-cron": "^3.0.3"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/utility/KeepAlive.js:
--------------------------------------------------------------------------------
1 | import http from 'http';
2 |
3 | export default function keepAlive() {
4 | http.createServer((req, res) => {
5 | res.write("I'm alive");
6 | res.end();
7 | }).listen(8080);
8 |
9 | console.log("Server is running on port 8080");
10 | }
--------------------------------------------------------------------------------
/utility/LeetCode.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 |
3 | function calculateStreak(submissionCalendar) {
4 | submissionCalendar = JSON.parse(submissionCalendar);
5 |
6 | const datesWithSubmissions = Object.entries(submissionCalendar)
7 | .filter(([ts, count]) => count > 0)
8 | .map(([ts]) => new Date(ts * 1000))
9 | .sort((a, b) => a - b);
10 |
11 | if (datesWithSubmissions.length === 0) return 0;
12 |
13 | let currentStreak = 0;
14 | let hasSolvedToday = false;
15 | const currentDate = new Date();
16 | const lastSubmissionDate = datesWithSubmissions[datesWithSubmissions.length - 1];
17 | // Check if the last submission was today
18 | if (lastSubmissionDate.setHours(0, 0, 0, 0) === currentDate.setHours(0, 0, 0, 0)) {
19 | hasSolvedToday = true;
20 | currentDate.setDate(currentDate.getDate() + 1);
21 | }
22 | // Calculate streak
23 | for (let i = datesWithSubmissions.length - 1; i >= 0; i--) {
24 | currentDate.setDate(currentDate.getDate() - 1);
25 | if (datesWithSubmissions[i].setHours(0, 0, 0, 0) === currentDate.setHours(0, 0, 0, 0)) {
26 | currentStreak += 1;
27 | } else {
28 | break;
29 | }
30 | }
31 | return { currentStreak, hasSolvedToday };
32 | }
33 |
34 | async function fetchLeetCodeProblems() {
35 | try {
36 | const response = await axios.get('https://leetcode.com/api/problems/all/');
37 | return response.data.stat_status_pairs.filter(problem => !problem.paid_only);
38 | } catch (error) {
39 | console.error('Error fetching LeetCode problems:', error);
40 | }
41 | }
42 |
43 | function chunkArray(array, size) {
44 | const chunked = [];
45 | for (let i = 0; i < array.length; i += size) {
46 | chunked.push(array.slice(i, i + size));
47 | }
48 | return chunked;
49 | }
50 |
51 |
52 | export default {
53 | calculateStreak,
54 | fetchLeetCodeProblems,
55 | chunkArray
56 | }
--------------------------------------------------------------------------------