From 7e65ae0e135098acad76b8081f34478b4efc077f Mon Sep 17 00:00:00 2001 From: Andrew Lee Date: Sat, 11 Jan 2025 18:02:05 -0500 Subject: Cleaned up some code, author image now support attachments --- api/routes/quotes.js | 58 +++++++++++++++++++++++++++++ api/server.js | 56 +--------------------------- commands/addquote.js | 87 ++++++++++++++++++++++++++----------------- commands/quote.js | 4 ++ commands/setup.js | 3 +- models/quote.js | 20 ++++++++++ storage/activities.js | 1 - web/astro.config.mjs | 2 +- web/src/components/Quotes.jsx | 1 + web/src/layouts/Layout.astro | 2 +- web/src/pages/index.astro | 19 +++++++++- 11 files changed, 159 insertions(+), 94 deletions(-) create mode 100644 api/routes/quotes.js diff --git a/api/routes/quotes.js b/api/routes/quotes.js new file mode 100644 index 0000000..39aba7b --- /dev/null +++ b/api/routes/quotes.js @@ -0,0 +1,58 @@ +const express = require('express'); +const quoteDB = require('../../models/quote.js'); + +const router = express.Router(); + +const pendingQuote = quoteDB.pendingQuote; +const approvedQuote = quoteDB.quote; + +router.get('/pending-quotes', async (req, res) => { + try { + const quotes = await pendingQuote.findAll(); + res.json(quotes); + } catch (error) { + console.error('Error fetching quotes:', error); + res.status(500).send('Internal Server Error'); + } +}); + +router.post('/approve-quote', async (req, res) => { + const { id } = req.body; + try { + const quote = await pendingQuote.findByPk(id); + if (quote) { + await approvedQuote.create({ + author: quote.author, + authorImage: quote.authorImage, + quote: quote.quote, + year: quote.year, + submitter: quote.submitterID + }); + await pendingQuote.destroy({ where: { id } }); + res.status(200).send('Quote approved'); + } else { + res.status(404).send('Quote not found'); + } + } catch (error) { + console.error('Error approving quote:', error); + res.status(500).send('Internal Server Error'); + } +}); + +router.post('/reject-quote', async (req, res) => { + const { id } = req.body; + try { + const quote = await pendingQuote.findByPk(id); + if (quote) { + await pendingQuote.destroy({ where: { id } }); + res.status(200).send('Quote rejected'); + } else { + res.status(404).send('Quote not found'); + } + } catch (error) { + console.error('Error rejecting quote:', error); + res.status(500).send('Internal Server Error'); + } +}); + +module.exports = router; diff --git a/api/server.js b/api/server.js index a918308..a6cb48c 100644 --- a/api/server.js +++ b/api/server.js @@ -1,6 +1,6 @@ const express = require('express'); const cors = require('cors'); -const { pendingQuote, quote: approvedQuote } = require('../models/quote.js'); +const quotesRouter = require('./routes/quotes'); const app = express(); const PORT = 3000; @@ -9,54 +9,7 @@ const createServer = () => { app.use(cors()); // Allow cross-origin requests app.use(express.json()); - // Endpoint to get all pending quotes - app.get('/api/pending-quotes', async (req, res) => { - try { - const quotes = await pendingQuote.findAll(); - res.json(quotes); - } catch (error) { - console.error('Error fetching quotes:', error); - res.status(500).send('Internal Server Error'); - } - }); - - app.post('/api/approve-quote', async (req, res) => { - const { id } = req.body; - try { - const quote = await pendingQuote.findByPk(id); - if (quote) { - await approvedQuote.create({ - author: quote.author, - quote: quote.quote, - year: quote.year, - authorImage: quote.authorImage - }); - await pendingQuote.destroy({ where: { id } }); - res.status(200).send('Quote approved'); - } else { - res.status(404).send('Quote not found'); - } - } catch (error) { - console.error('Error approving quote:', error); - res.status(500).send('Internal Server Error'); - } - }); - - app.post('/api/reject-quote', async (req, res) => { - const { id } = req.body; - try { - const quote = await pendingQuote.findByPk(id); - if (quote) { - await pendingQuote.destroy({ where: { id } }); - res.status(200).send('Quote rejected'); - } else { - res.status(404).send('Quote not found'); - } - } catch (error) { - console.error('Error rejecting quote:', error); - res.status(500).send('Internal Server Error'); - } - }); + app.use('/api', quotesRouter); app.get('/api/version', (req, res) => { const { abVersion } = require('../storage/settings.json'); @@ -64,11 +17,6 @@ const createServer = () => { }); - app.get('/' , (req, res) => { - res.send('API for AleeBot'); - // Most likely going to redirect to the frontend - }); - // Start the server app.listen(PORT, () => { console.log(`Server is running on http://localhost:${PORT}`); diff --git a/commands/addquote.js b/commands/addquote.js index 52f7b21..f13d3c9 100644 --- a/commands/addquote.js +++ b/commands/addquote.js @@ -20,14 +20,21 @@ const { pendingQuote } = require('../models/quote'); const { MessageEmbed } = require("discord.js"); +const setupUsers = new Set(); + module.exports.run = async (client, message) => { try { let newAuthor, newAuthorImage, newQuote, newYear; - let isSetupRunning = false; + + if (setupUsers.has(message.author.id)) { + return await message.reply('You are already setting up a quote.'); + } + + setupUsers.add(message.author.id); const setupProcess = [ 'Provide the name of the author:', - 'Submit the image of the author:\nYou need to use a picture link that ends in .jpg or .png (like those from IMGUR or Google Images), and the picture should be either 128x128 pixels or 512x512 pixels in size.', + 'Submit the image of the author:\nYou can use an attachment or a link that ends in .jpg/.jpeg or .png (like those from IMGUR or Google Images), and the picture should be either 128x128 pixels or 512x512 pixels in size.', 'Enter the quote:', 'Specify the year from which the quote originates:' ]; @@ -38,23 +45,38 @@ module.exports.run = async (client, message) => { authorImage: newAuthorImage, quote: newQuote, year: newYear, + submitterAuthor: message.author.username, + submitterID: message.author.id }); } + async function imageCheck(message) { + const attachment = message.attachments.first(); + if (attachment) { + const fileExtension = attachment.name.split('.').pop().toLowerCase(); + if (['jpg', 'png', 'jpeg'].includes(fileExtension)) { + newAuthorImage = attachment.url.toString(); // Use the attachment's URL directly + } else { + await dmChannel.send('Invalid file type. Please attach a .jpg or .png image.'); + return await imageCheck(message); + } + } else if (msg.content.startsWith('http') && (message.content.endsWith('.jpg') || message.content.endsWith('.jpeg') || message.content.endsWith('.png'))) { + newAuthorImage = msg.content; // Use the provided URL + } else { + await dmChannel.send('Invalid input. Please provide an image URL or attach an image file.'); + return await imageCheck(message); + } + } + let setupMessage = "Welcome to the AleeBot Quote Setup!\n"; setupMessage += "Please follow these rules when submitting quotes:\n"; - setupMessage += "```1. Do not use profanity or offensive language.\n"; + setupMessage += "```1. No offensive content (NSFW, Racism, etc).\n"; setupMessage += "2. Do not send any personal information.\n"; setupMessage += "3. Only send noteworthy quotes.```\n"; setupMessage += "We reserve the right to reject any quotes that do not meet our criteria.\n"; - if (isSetupRunning) { - return await message.reply('You are already setting up a quote.'); - } - const filter = (m) => m.author.id === message.author.id; - isSetupRunning = true; await message.reply(':arrow_left: Check DMs to continue.'); const dmChannel = await message.author.createDM(); @@ -65,10 +87,14 @@ module.exports.run = async (client, message) => { const collector = dmChannel.createMessageCollector({ filter, max: setupProcess.length, - time: 1000 * 120 + time: 1000 * 1200 }); - collector.on('collect', async () => { + collector.on('collect', async (msg) => { + if (counter === 2) { // Collecting author image + await imageCheck(msg); + } + if (counter < setupProcess.length) { await dmChannel.send(setupProcess[counter++]); } @@ -77,20 +103,23 @@ module.exports.run = async (client, message) => { collector.on('end', async (collected) => { if (collected.size < setupProcess.length) { dmChannel.send('Quote setup was not completed. Please rerun the command.'); + setupUsers.delete(message.author.id); } else { const quoteContent = collected.map((m) => m.content); newAuthor = quoteContent[0]; - newAuthorImage = quoteContent[1]; + if (!newAuthorImage) { + newAuthorImage = quoteContent[1] || 'N/A'; + } newQuote = quoteContent[2]; newYear = quoteContent[3]; const setupEmbed = new MessageEmbed() .setAuthor('AleeBot Quote Setup', client.user.avatarURL()) - .setDescription('Are you happy with this quote?\nThis quote will be sent for manual approval automatically in 2 minutes.') - .addField('Author', newAuthor) - .addField('Author Image (URL)', newAuthorImage) - .addField('Quote', newQuote) - .addField('Year', newYear) + .setDescription('Are you happy with this quote?\nThis quote will be sent for manual approval automatically in 20 minutes.') + .addField('Author', newAuthor || 'N/A') + .addField('Author Image (URL)', newAuthorImage || 'N/A') + .addField('Quote', newQuote || 'N/A') + .addField('Year', newYear || 'N/A') .setColor('#1fd619'); let messageReact = await dmChannel.send({embeds: [setupEmbed]}); @@ -107,7 +136,7 @@ module.exports.run = async (client, message) => { const reactionCollector = messageReact.createReactionCollector({ filter: reactionFilter, - time: 1000 * 120 + time: 1000 * 1200 }); reactionCollector.on('collect', async (reaction) => { @@ -119,10 +148,10 @@ module.exports.run = async (client, message) => { await dmChannel.send('Updated author name.'); break; case '📷': - await dmChannel.send('You selected the author image. Please provide the image URL.'); + await dmChannel.send('You selected the author image. Please provide the image URL or attach an image file.'); const imageResponse = await dmChannel.awaitMessages({ filter, max: 1, time: 60000 }); - if (imageResponse.size) newAuthorImage = imageResponse.first().content; - await dmChannel.send('Updated author URL.'); + await imageCheck(imageResponse.first()); + await dmChannel.send('Updated author image.'); break; case '🖋️': await dmChannel.send('You selected the quote. Please provide the quote.'); @@ -144,36 +173,26 @@ module.exports.run = async (client, message) => { break; } - const updatedEmbed = new MessageEmbed() - .setAuthor('AleeBot Quote Setup', client.user.avatarURL()) - .setDescription('Are you happy with this quote?\nThis quote will be sent for manual approval automatically in 2 minutes.') - .addField('Author', newAuthor) - .addField('Author Image (URL)', newAuthorImage) - .addField('Quote', newQuote) - .addField('Year', newYear) - .setColor('#1fd619'); - - await messageReact.edit({embeds: [updatedEmbed]}); + await messageReact.edit({embeds: [setupEmbed]}); }); reactionCollector.on('end', async (collected, reason) => { if (reason === 'cancelled') { - isSetupRunning = false; dmChannel.send('Cancelling quote setup.'); } else if (reason === 'completed') { dmChannel.send('Sending this quote for manual approval.'); - isSetupRunning = false; await createQuote(); } else { dmChannel.send('You have not responded. Sending this quote for manual approval.'); - isSetupRunning = false; await createQuote(); } + setupUsers.delete(message.author.id); }); - } }); } catch (error) { + message.author.send('An error occurred while setting up the quote. Please try again.'); + setupUsers.delete(message.author.id); console.error(error); } }; diff --git a/commands/quote.js b/commands/quote.js index 02699cd..0d67b66 100644 --- a/commands/quote.js +++ b/commands/quote.js @@ -38,6 +38,10 @@ module.exports.run = async (client, message, args) => { .setColor('#1fd619') .setFooter('- ' + quote.year); + if (quote.url) { + quoteEmbed.setURL(quote.url); + } + await message.reply({ embeds: [quoteEmbed] }) } else { message.reply('Cannot find quote, specify the correct quote id.'); diff --git a/commands/setup.js b/commands/setup.js index 380e85e..c797f7d 100644 --- a/commands/setup.js +++ b/commands/setup.js @@ -27,7 +27,8 @@ module.exports.run = async (client, message) => { .setDescription('Select the options') .addField('Logging', 'channelid', true) .addField('Broadcast', 'placeholder', true) - .addField('Broadcast', 'placeholder', true); + .addField('Quote of the Day', 'placeholder', true) + .addField('QOTD Channel', 'channelid', true); message.author.send({embeds: [setupEmbed]}); }; diff --git a/models/quote.js b/models/quote.js index eab4f57..b68dfbc 100644 --- a/models/quote.js +++ b/models/quote.js @@ -22,6 +22,14 @@ const quote = sequelize.define('quotes', { year: { type: Sequelize.STRING, allowNull: false + }, + url: { + type: Sequelize.STRING, + allowNull: true + }, + submitter: { + type: Sequelize.STRING, + allowNull: false } }) @@ -47,6 +55,18 @@ const pendingQuote = sequelize.define('pending-quotes', { year: { type: Sequelize.STRING, allowNull: false + }, + url: { + type: Sequelize.STRING, + allowNull: true + }, + submitterAuthor: { + type: Sequelize.STRING, + allowNull: false + }, + submitterID: { + type: Sequelize.STRING, + allowNull: false } }) diff --git a/storage/activities.js b/storage/activities.js index 5d0161e..be28e58 100644 --- a/storage/activities.js +++ b/storage/activities.js @@ -91,7 +91,6 @@ const activities = [ 'What is Web3?', 'GNU\'s NOT UNIX!', 'Linux, but actually GNU/Linux', - 'Praise RMS! (dont)', 'Debloating my ThinkPad', 'Turbotastic!', `Now running on Discord.JS ${version}!` diff --git a/web/astro.config.mjs b/web/astro.config.mjs index 53e49de..515ddf0 100644 --- a/web/astro.config.mjs +++ b/web/astro.config.mjs @@ -6,4 +6,4 @@ import react from '@astrojs/react'; // https://astro.build/config export default defineConfig({ integrations: [react()] -}); \ No newline at end of file +}); diff --git a/web/src/components/Quotes.jsx b/web/src/components/Quotes.jsx index 1d563e7..831408d 100644 --- a/web/src/components/Quotes.jsx +++ b/web/src/components/Quotes.jsx @@ -72,6 +72,7 @@ export function PendingQuotes() {

{quote.quote}

- {quote.year} + Submitted by {quote.submitterAuthor} ({quote.submitterID}) diff --git a/web/src/layouts/Layout.astro b/web/src/layouts/Layout.astro index e455c61..2f6032d 100644 --- a/web/src/layouts/Layout.astro +++ b/web/src/layouts/Layout.astro @@ -5,7 +5,7 @@ - Astro Basics + AleeBot Web Interface diff --git a/web/src/pages/index.astro b/web/src/pages/index.astro index b5c607d..8136402 100644 --- a/web/src/pages/index.astro +++ b/web/src/pages/index.astro @@ -2,12 +2,11 @@ import Layout from '../layouts/Layout.astro'; import { PendingQuotes } from '../components/Quotes'; -const version = await fetch('http://localhost:3000/api/version').then(res => res.json()); ---
-

AleeBot {version}

+

AleeBot

@@ -27,3 +26,19 @@ const version = await fetch('http://localhost:3000/api/version').then(res => res } + + -- cgit v1.2.3