import BotCommand from "../../libs/BotCommand"; import BotClient from "../../libs/BotClient"; import { ChatInputCommandInteraction, MessageFlags, EmbedBuilder, AttachmentBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle, TextChannel } from "discord.js"; import QuestionScheduler from "../../libs/QuestionScheduler"; import AnswerGrader from "../../libs/AnswerGrader"; const SUBJECTS = ["Mathematics", "Physics", "Chemistry", "Biology", "Computer Science", "Engineering"]; export default class WeeklyCommand extends BotCommand { constructor() { super("weekly", "Answer this week's weekly STEM question", "/weekly"); this.data.addStringOption(option => option.setName("subject") .setDescription("Choose a subject") .addChoices( { name: "Mathematics", value: "mathematics" }, { name: "Physics", value: "physics" }, { name: "Chemistry", value: "chemistry" }, { name: "Organic Chemistry", value: "organic chemistry" }, { name: "Biology", value: "biology" }, { name: "Computer Science", value: "computer science" }, { name: "Engineering", value: "engineering" } ) .setRequired(true) ); } override async execute(Discord: any, client: BotClient, interaction: ChatInputCommandInteraction): Promise { try { await interaction.deferReply({ flags: MessageFlags.Ephemeral }); const subject = interaction.options.getString("subject", true); const scheduler = new QuestionScheduler(); await scheduler.initialize(); const question = await scheduler.getQuestionForPeriod(subject, "weekly"); if (!question) { return interaction.editReply({ content: `Failed to load this week's ${subject} question. Please try again later.` }); } const hasAnswered = await scheduler.hasUserAnswered(interaction.user.id, question.id); if (hasAnswered) { return interaction.editReply({ content: "You've already submitted an answer for this week's question! Check back next Sunday." }); } const imagePath = await client.typst.renderToImage(question.typst_source); if (!imagePath) { // Log error to logs channel const logsChannelId = process.env.LOGS_CHANNEL_ID; if (logsChannelId) { try { const logsChannel = await client.channels.fetch(logsChannelId) as TextChannel; if (logsChannel?.isTextBased()) { const errorEmbed = new EmbedBuilder() .setTitle("❌ Typst Render Error - Weekly Question") .setColor(0xef4444) .addFields( { name: "Subject", value: subject, inline: true }, { name: "Question ID", value: question.id, inline: true }, { name: "Topic", value: question.topic, inline: false }, { name: "User", value: `<@${interaction.user.id}>`, inline: true }, { name: "Error", value: "Failed to render Typst image", inline: false } ) .setTimestamp(); await logsChannel.send({ embeds: [errorEmbed] }); } } catch (logErr) { console.error("Failed to send error to logs channel:", logErr); } } return interaction.editReply({ content: `❌ An error occurred while generating the question image. This has been reported to administrators.` }); } const safeSubject = subject.replace(/\s+/g, '_'); const embed = new EmbedBuilder() .setTitle(`🎓 Weekly ${subject} Question (PhD Level)`) .setDescription(`**Topic:** ${question.topic}\n**Difficulty:** ${question.difficulty_rating}`) .setColor(0xfacc15) .setImage(`attachment://weekly_${safeSubject}.png`) .setFooter({ text: `Resets at 12 AM Sunday` }) .setTimestamp(); const attachment = new AttachmentBuilder(imagePath, { name: `weekly_${safeSubject}.png` }); const row = new ActionRowBuilder() .addComponents( new ButtonBuilder() .setCustomId(`weekly_answer_${question.id}`) .setLabel("Submit Answer") .setStyle(ButtonStyle.Primary) .setEmoji("✍️"), new ButtonBuilder() .setCustomId(`weekly_report_${question.id}`) .setLabel("Report Issue") .setStyle(ButtonStyle.Danger) .setEmoji("⚠️") ); await interaction.editReply({ embeds: [embed], files: [attachment], components: [row], }); } catch (err) { console.error("Error executing weekly command:", err); try { if (interaction.deferred || interaction.replied) { await interaction.editReply({ content: "An error occurred. Please try again later." }); } else { await interaction.reply({ content: "An error occurred. Please try again later.", flags: MessageFlags.Ephemeral }); } } catch (e) { console.error("Error sending error reply:", e); } } } }