duebot-test / src /bot /index.js
Ali00922's picture
Upload 12 files
c4be319 verified
const { Client, GatewayIntentBits, Partials, Events, REST, Routes } = require('discord.js');
const mongoose = require('mongoose');
const User = require('../models/User');
const { encrypt } = require('../utils/crypto');
const client = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.DirectMessages,
GatewayIntentBits.MessageContent
],
partials: [Partials.Channel, Partials.Message]
});
const commands = [
{
name: 'status',
description: 'Check if DueBot is online and active.'
},
{
name: 'login',
description: 'Securely link your Moodle LMS account.',
options: [
{
name: 'username',
description: 'Your Moodle username',
type: 3, // STRING
required: true
},
{
name: 'password',
description: 'Your Moodle password',
type: 3, // STRING
required: true
}
]
},
{
name: 'deadlines',
description: 'View all your currently tracking deadlines.'
},
{
name: 'sync',
description: 'Force an immediate sync with your Moodle LMS to check for deadlines right now.'
}
];
const rest = new REST({ version: '10' }).setToken(process.env.DISCORD_TOKEN);
client.once(Events.ClientReady, async (c) => {
console.log(`Ready! Logged in as ${c.user.tag}`);
try {
await rest.put(
Routes.applicationCommands(c.user.id),
{ body: commands }
);
console.log('Successfully registered slash commands.');
} catch (error) {
console.error('Error registering slash commands:', error);
}
});
client.on(Events.InteractionCreate, async interaction => {
if (!interaction.isChatInputCommand()) return;
if (interaction.commandName === 'status') {
await interaction.reply({ content: 'DueBot is online, fully synced, and running smoothly πŸš€', ephemeral: true });
}
if (interaction.commandName === 'login') {
const username = interaction.options.getString('username');
const password = interaction.options.getString('password');
const discordId = interaction.user.id;
try {
await interaction.deferReply({ ephemeral: true });
// Encrypt passwords using AES-256
const userEncrypted = encrypt(username);
const passEncrypted = encrypt(password);
let user = await User.findOne({ discordId });
if (user) {
user.lmsUsername = userEncrypted.encryptedData;
user.lmsPassword = passEncrypted.encryptedData;
user.iv = passEncrypted.iv; // both encrypt calls generate different IVs. For simplicity, we can use the same logic or store them.
// Let's refine the schema saving here
await user.save();
await interaction.editReply('Your Moodle credentials have been updated successfully and encrypted in the database πŸ›‘οΈ. Scraping will begin soon!');
} else {
user = new User({
discordId,
lmsUsername: userEncrypted.encryptedData,
lmsPassword: passEncrypted.encryptedData,
iv: passEncrypted.iv // Need to modify slightly to use one IV or store 2.
});
// actually wait, let's just store password's IV and username IV if needed.
// Or don't encrypt username. Let's just encrypt password. Username is often public/student id.
// For MVP, we'll store lmsUsername raw, just password encrypted to fix the IV issue purely.
user.lmsUsername = username;
await user.save();
await interaction.editReply('Your Moodle account was successfully linked and your password heavily encrypted πŸ›‘οΈ! DueBot will now scrape your deadlines.');
}
} catch (error) {
console.error(error);
await interaction.editReply('There was an error saving your credentials. Please try again later.');
}
}
if (interaction.commandName === 'deadlines') {
const Deadline = require('../models/Deadline');
const { getPriorityEmoji } = require('../utils/priorityEmoji');
const discordId = interaction.user.id;
try {
// Update to pull BOTH Pending and Missed deadlines so the πŸ’€ emoji works
const deadlines = await Deadline.find({ discordId, status: { $in: ['Pending', 'Missed'] } }).sort({ deadlineTime: 1 });
if (deadlines.length === 0) {
await interaction.reply({ content: 'Hooray! You have no tracking deadlines πŸŽ‰.', ephemeral: true });
return;
}
const now = new Date();
const missed = deadlines.filter(dl => dl.deadlineTime < now);
const upcoming = deadlines.filter(dl => dl.deadlineTime >= now);
let msg = '';
let chunks = [];
const addText = (text) => {
if (msg.length + text.length > 1900) {
chunks.push(msg);
msg = '';
}
msg += text;
};
if (missed.length > 0) {
addText('**πŸ’€ Missed / Overdue Assignments:**\n\n');
missed.forEach((dl, i) => {
const timeStr = dl.deadlineTime.toLocaleString();
addText(`**${i + 1}.** πŸ’€ **${dl.assignmentTitle}**\n*Course: ${dl.courseName}*\n*Was due at: ${timeStr}*\n\n`);
});
}
if (upcoming.length > 0) {
if (missed.length > 0) addText('---\n\n'); // Add a nice separator between missed and upcoming
addText('**πŸ“… Your Upcoming Deadlines:**\n\n');
upcoming.forEach((dl, i) => {
const timeStr = dl.deadlineTime.toLocaleString();
const emoji = getPriorityEmoji(dl.deadlineTime);
// Maintain continuous numbering (e.g., 3, 4, 5...)
const num = missed.length + i + 1;
addText(`**${num}.** ${emoji} **${dl.assignmentTitle}**\n**Course:** ${dl.courseName}\n**Due at:** ${timeStr}\n\n`);
});
}
if (msg.length > 0) chunks.push(msg);
for (let j = 0; j < chunks.length; j++) {
if (j === 0) {
await interaction.reply({ content: chunks[0], ephemeral: true });
} else {
await interaction.followUp({ content: chunks[j], ephemeral: true });
}
}
} catch (error) {
console.error(error);
await interaction.reply({ content: 'Error fetching deadlines.', ephemeral: true });
}
}
if (interaction.commandName === 'sync') {
const { scrapeDeadlines } = require('../services/scraper.service');
const { syncUserDeadlines } = require('../services/deadlineSync.service');
try {
await interaction.reply({ content: 'πŸ•΅οΈβ€β™‚οΈ **Initializing manual sync with NUST Moodle...** This might take about 15-20 seconds. I will DM you when done!', ephemeral: true });
const user = await User.findOne({ discordId: interaction.user.id });
if (!user) {
await interaction.user.send("You must `/login` first before syncing!").catch(() => {});
return;
}
const rawDeadlines = await scrapeDeadlines(user.lmsUsername, user.lmsPassword, user.iv);
await syncUserDeadlines(user, rawDeadlines);
// Update user's last scraped timestamp globally
user.lastScrapedAt = new Date();
await user.save();
await interaction.user.send("βœ… **Sync Complete!** Your deadlines are now perfectly up to date with Moodle.").catch(() => {});
} catch (error) {
console.error('[Bot /sync] Error:', error);
await interaction.user.send("❌ **Sync Failed.** There was an error reaching Moodle. Please try again later.").catch(() => {});
}
}
});
module.exports = client;