Ability to add quotes, web interface, pending quotes

This commit is contained in:
Andrew Lee 2025-01-11 11:55:18 -05:00
parent 83dcca0a02
commit f5de90ba89
28 changed files with 527 additions and 382 deletions

78
api/server.js Normal file
View file

@ -0,0 +1,78 @@
const express = require('express');
const cors = require('cors');
const { pendingQuote, quote: approvedQuote } = require('../models/quote.js');
const app = express();
const PORT = 3000;
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.get('/api/version', (req, res) => {
const { abVersion } = require('../storage/settings.json');
res.json(abVersion);
});
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}`);
});
};
module.exports = createServer;

View file

@ -1,7 +1,7 @@
/** **************************************
*
* AleeBot: Made for discord servers
* Copyright (C) 2017-2022 Andrew Lee Projects
* Copyright (C) 2017-2025 Andrew Lee Projects
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -24,17 +24,16 @@ const client = new Discord.Client({
parse: ['users', 'roles'],
repliedUser: true
},
intents: ['GUILDS', 'GUILD_MESSAGES', 'GUILD_MEMBERS', 'GUILD_MESSAGE_REACTIONS']
intents: ['GUILDS', 'GUILD_MESSAGES', 'GUILD_MEMBERS', 'GUILD_MESSAGE_REACTIONS', 'DIRECT_MESSAGES', 'DIRECT_MESSAGE_REACTIONS']
});
const moment = require('moment');
const express = require('express');
const fs = require('fs');
const readline = require('readline');
const colors = require('colors');
//const i18next = require('i18next');
const web = express();
const settings = require('./storage/settings.json');
const { activity } = require('./storage/activities');
const createServer = require("./api/server");
const active = new Map();
let autoRole = true;
let readyEmbedMessage = true;
@ -50,11 +49,11 @@ const log = (message) => {
function botPresence() {
client.user.setPresence({
activities: [{
name: activity[Math.floor(Math.random() * activity.length)]
}],
status: 'online',
afk: false,
activities: [{
name: activity[Math.floor(Math.random() * activity.length)]
}],
status: 'online',
afk: false,
});
log(`[>] Updated bot presence to "${client.user.presence.activities[0].name}"`.green);
}
@ -65,7 +64,7 @@ const rl = readline.createInterface({
prompt: '> '.gray,
});
console.log(`AleeBot ${settings.abVersion}: Copyright (C) 2017-2023 Andrew Lee Projects`.gray);
console.log(`AleeBot ${settings.abVersion}: Copyright (C) 2017-2025 Andrew Lee Projects`.gray);
console.log('This program comes with ABSOLUTELY NO WARRANTY; for details type `show w\'.'.gray);
console.log('This is free software, and you are welcome to redistribute it'.gray);
console.log('under certain conditions; type `show c\' for details.\n'.gray);
@ -219,13 +218,7 @@ client.on('ready', async () => {
botPresence();
web.get('/', (req, res) => {
res.send("Hello World! This is going to become the AleeBot dashboard...");
});
web.listen(process.env.port, () => {
console.log(`Listening at https://localhost:${process.env.port}`)
})
createServer();
setInterval(function() {
botPresence();
@ -286,7 +279,7 @@ client.on('guildMemberRemove', (member) => {
client.on('messageUpdate', async (oldMessage, newMessage) => {
if (oldMessage.guild.id !== serverWhitelist) return;
if (!oldMessage.guild || oldMessage.guild.id !== serverWhitelist) return;
if (oldMessage.content === newMessage.content) {
return;
}
@ -407,6 +400,7 @@ client.on("messageReactionAdd", async (reaction, user) => {
client.on('messageCreate', async(msg) => {
if (!client.application?.owner) await client.application?.fetch();
if (msg.author.bot) return;
if (!msg.guild) return;
const prefixes = JSON.parse(fs.readFileSync('./storage/prefixes.json', 'utf8'));

View file

@ -17,7 +17,6 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* *************************************/
const {MessageButton} = require("discord.js");
module.exports.run = async (client, message) => {
const { MessageEmbed, MessageButton, MessageActionRow } = require('discord.js');
@ -30,7 +29,7 @@ module.exports.run = async (client, message) => {
.addField('About AleeBot', 'AleeBot is an all-in-one bot that\'s made from the Discord.JS API!')
.addField('License', 'GNU General Public License v3.0')
.addField('Contributors', Contributors)
.setFooter('© Copyright 2017-2023 Andrew Lee Projects')
.setFooter('© Copyright 2017-2025 Andrew Lee Projects')
.setColor('#1fd619');
let Buttons = new MessageActionRow()

View file

@ -17,105 +17,164 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* *************************************/
const quoteDB = require('../models/quote');
const { pendingQuote } = require('../models/quote');
const { MessageEmbed } = require("discord.js");
module.exports.run = async (client, message, args) => {
if (!['242775871059001344'].includes(message.author.id)) return message.reply('**This command is disabled due to a new system being implemented.**');
module.exports.run = async (client, message) => {
try {
let newAuthor;
let newAuthorImage;
let newQuote;
let newYear;
let quoteOriginator;
let newAuthor, newAuthorImage, newQuote, newYear;
let isSetupRunning = false;
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.',
'Enter the quote',
'Specify the year from which the quote originates.'
]
'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.',
'Enter the quote:',
'Specify the year from which the quote originates:'
];
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 += "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"
async function createQuote() {
await pendingQuote.create({
author: newAuthor,
authorImage: newAuthorImage,
quote: newQuote,
year: newYear,
});
}
let counter = 0
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 += "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 the quote.');
return await message.reply('You are already setting up a quote.');
}
const filter = m => m.author.id === message.author.id
const filter = (m) => m.author.id === message.author.id;
isSetupRunning = true;
await message.reply(':arrow_left: Check your DMs to continue.')
await message.author.send(setupMessage);
await message.author.send(setupProcess[counter++]);
await message.reply(':arrow_left: Check DMs to continue.');
const collector = message.channel.createMessageCollector({
const dmChannel = await message.author.createDM();
await dmChannel.send(setupMessage);
await dmChannel.send(setupProcess[0]);
let counter = 1;
const collector = dmChannel.createMessageCollector({
filter,
max: setupProcess.length,
time: 1000 * 60
time: 1000 * 120
});
collector.on('collect', message => {
console.log(`Collected ${message.content} from ${message.author.tag}`)
if (setupProcess.length > setupProcess.length + 1) {
message.author.send(setupProcess[counter++]);
collector.on('collect', async () => {
if (counter < setupProcess.length) {
await dmChannel.send(setupProcess[counter++]);
}
});
collector.on('end', collected => {
if (collected.size === 0 && collected.size < 2) {
message.author.send('Quote setup was not completed, rerun the command.')
collector.on('end', async (collected) => {
if (collected.size < setupProcess.length) {
dmChannel.send('Quote setup was not completed. Please rerun the command.');
} else {
let quoteContent = [];
collected.forEach((message) => {
quoteContent.push(message.content)
})
newAuthor = quoteContent[0]
newAuthorImage = quoteContent[1]
newQuote = quoteContent[2]
newYear = quoteContent[3]
const quoteContent = collected.map((m) => m.content);
newAuthor = quoteContent[0];
newAuthorImage = quoteContent[1];
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')
.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);
.addField('Year', newYear)
.setColor('#1fd619');
let messageReact = await dmChannel.send({embeds: [setupEmbed]});
await messageReact.react('🧑');
await messageReact.react('📷');
await messageReact.react('🖋️');
await messageReact.react('📅');
await messageReact.react('✅');
await messageReact.react('❌');
const reactionFilter = (reaction, user) => {
return ['🧑', '📷', '🖋️', '📅', '✅', '❌'].includes(reaction.emoji.name) && user.id === message.author.id;
};
const reactionCollector = messageReact.createReactionCollector({
filter: reactionFilter,
time: 1000 * 120
});
reactionCollector.on('collect', async (reaction) => {
switch (reaction.emoji.name) {
case '🧑':
await dmChannel.send('You selected the author. Please provide the name of the author.');
const authorResponse = await dmChannel.awaitMessages({ filter, max: 1, time: 60000 });
if (authorResponse.size) newAuthor = authorResponse.first().content;
await dmChannel.send('Updated author name.');
break;
case '📷':
await dmChannel.send('You selected the author image. Please provide the image URL.');
const imageResponse = await dmChannel.awaitMessages({ filter, max: 1, time: 60000 });
if (imageResponse.size) newAuthorImage = imageResponse.first().content;
await dmChannel.send('Updated author URL.');
break;
case '🖋️':
await dmChannel.send('You selected the quote. Please provide the quote.');
const quoteResponse = await dmChannel.awaitMessages({ filter, max: 1, time: 60000 });
if (quoteResponse.size) newQuote = quoteResponse.first().content;
await dmChannel.send('Updated quote.');
break;
case '📅':
await dmChannel.send('You selected the year. Please provide the year.');
const yearResponse = await dmChannel.awaitMessages({ filter, max: 1, time: 60000 });
if (yearResponse.size) newYear = yearResponse.first().content;
await dmChannel.send('Updated year.');
break;
case '✅':
reactionCollector.stop('completed');
break;
case '❌':
reactionCollector.stop('cancelled');
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]});
});
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();
}
});
message.author.send({embeds:[setupEmbed]})
quoteOriginator = message.author.tag
console.log(`This quote has been originated from ${quoteOriginator}`)
isSetupRunning = false;
}
});
/*await quoteDB.create({
author: newAuthor,
authorImage: newAuthorImage,
quote: newQuote,
year: newYear,
});*/
//let messageReact = await message.author.send({embeds: [setupEmbed]});
/*await messageReact.react('🧑');
await messageReact.react('📷');
await messageReact.react('🖋️');
await messageReact.react('📅');*/
} catch (error) {
console.log(error)
console.error(error);
}
};
@ -123,6 +182,7 @@ exports.conf = {
aliases: [],
guildOnly: true,
};
exports.help = {
name: 'addquote',
description: 'Adds a quote to the database.',

View file

@ -18,7 +18,7 @@
*
* *************************************/
module.exports.run = async (client, message, args) => {
const quoteDB = require('../models/quote');
const { quote: quoteDB } = require('../models/quote');
const { MessageEmbed } = require('discord.js');
let quoteID = args[0];
@ -38,7 +38,7 @@ module.exports.run = async (client, message, args) => {
.setColor('#1fd619')
.setFooter('- ' + quote.year);
await message.reply({ content: 'Alright, here\'s your quote.', embeds: [quoteEmbed] })
await message.reply({ embeds: [quoteEmbed] })
} else {
message.reply('Cannot find quote, specify the correct quote id.');
}

View file

@ -20,14 +20,12 @@
module.exports.run = async (client, message) => {
if (!['242775871059001344', message.guild.ownerId].includes(message.author.id)) return message.reply(':warning: You must be a server owner or be the creator of the bot to access this command.');
message.reply('Look at your DMs.');
//message.reply("This feature is coming soon. Stay tuned!");
message.reply(':arrow_left: Check DMs to continue.');
const Discord = require('discord.js');
const setupEmbed = new Discord.MessageEmbed()
.setTitle('AleeBot Setup', client.user.avatarURL())
.setDescription('Select the options')
.addField('Chat Logs', 'channelid', true)
.addField('Joining & Leaving Logs', 'placeholder', true)
.addField('Logging', 'channelid', true)
.addField('Broadcast', 'placeholder', true)
.addField('Broadcast', 'placeholder', true);

View file

@ -1,50 +0,0 @@
/** **************************************
*
* Balance: Command for AleeBot
* Copyright (C) 2017-2021 Alee Productions
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* *************************************/
module.exports.run = async (client, message) => {
const db = require('quick.db');
const {MessageEmbed} = require('discord.js');
const user = message.mentions.users.first() || message.author;
let balance = await db.fetch(`userBalance_${user.id}`);
if (balance === null) {
db.set(`userBalance_${message.author.id}`, 0);
balance = 0;
}
const embed = new MessageEmbed()
.setDescription('**AleeCorp Bank**')
.addField('Account Holder: ', user.username, true)
.addField('Account Balance: ', balance, true)
.setColor('#1fd619');
message.channel.send({embed});
};
exports.conf = {
aliases: ['bal', 'money'],
guildOnly: false,
};
exports.help = {
name: 'balance',
description: 'Checks the balance of AleeBot',
usage: 'balance [@someone (optional)]',
category: '- Economy Commands',
};

View file

@ -1,114 +0,0 @@
/** **************************************
*
* Buy: Command for AleeBot
* Copyright (C) 2017-2021 Alee Productions
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* *************************************/
module.exports.run = async (client, message, args) => {
const Discord = require('discord.js');
const fs = require('fs')
const db = require('quick.db');
const items = JSON.parse(fs.readFileSync('./storage/items.json', 'utf8'));
let categories = [];
if (!args.join(" ")) {
for (var i in items) {
if (!categories.includes(items[i].type)) {
categories.push(items[i].type)
}
}
const embed = new Discord.MessageEmbed()
.setDescription(`Available Items`)
.setColor('#1fd619')
for (var i = 0; i < categories.length; i++) {
var tempDesc = '';
for (var c in items) {
if (categories[i] === items[c].type) {
tempDesc += `${items[c].name} - ${items[c].price}$ - ${items[c].desc}\n`;
}
}
embed.addField(categories[i], tempDesc);
}
return message.channel.send({
embed
});
}
let itemName = '';
let itemPrice = 0;
let itemDesc = '';
for (var i in items) {
if (args.join(" ").trim().toUpperCase() === items[i].name.toUpperCase()) {
itemName = items[i].name;
itemPrice = items[i].price;
itemDesc = items[i].desc;
}
}
if (itemName === '') {
return message.channel.send(`Item ${args.join(" ").trim()} not found.`)
}
let selfBalance = await db.fetch(`userBalance_${message.author.id}`);
if (selfBalance === null) {
db.set(`userBalance_${message.author.id}`, 0);
selfBalance = 0
}
if (itemPrice > selfBalance) return message.reply('You don\'t have enough money for this item.')
db.subtract(`userBalance_${message.author.id}`, itemPrice);
if (itemName === 'Programmer Role') {
message.guild.members.get(message.author.id).addRole(message.guild.roles.find("name", "Programmers"));
}
message.channel.send('You bought ' + itemName + '!');
};
exports.conf = {
aliases: [],
guildOnly: false,
};
exports.help = {
name: 'buy',
description: 'Buy things.',
usage: 'buy [item]',
category: '- Economy Commands',
};

View file

@ -1,56 +0,0 @@
/** **************************************
*
* Daily: Command for AleeBot
* Copyright (C) 2017-2021 Alee Productions
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* *************************************/
const db = require('quick.db');
ms = require('parse-ms');
module.exports.run = async (client, message) => {
const cooldown = 8.64e+7;
const amount = 100;
const lastDaily = await db.fetch(`lastDaily_${message.author.id}`);
if (lastDaily !== null && cooldown - (Date.now() - lastDaily) > 0) {
const timeObj = ms(cooldown - (Date.now() - lastDaily));
message.reply(`You already collected your money, please wait **${timeObj.hours}h ${timeObj.minutes}m**!`);
} else {
message.channel.send(`You have successfully collected $${amount} dollars!`);
const balance = await db.fetch(`userBalance_${message.author.id}`);
if (balance == null) {
db.set(`userBalance_${message.author.id}`, 0);
}
db.set(`lastDaily_${message.author.id}`, Date.now());
db.add(`userBalance_${message.author.id}`, 100);
}
};
exports.conf = {
aliases: [],
guildOnly: false,
};
exports.help = {
name: 'daily',
description: 'This gives you money everyday.',
usage: 'daily',
category: '- Economy Commands',
};

View file

@ -1,60 +0,0 @@
/** **************************************
*
* Pay: Command for AleeBot
* Copyright (C) 2017-2021 Alee Productions
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* *************************************/
module.exports.run = async (client, message, args) => {
const db = require('quick.db');
if (!message.mentions.members.first()) return message.reply('Please mention a user...');
const targetMember = message.mentions.members.first();
const amount = parseInt(args.join(' ').replace(targetMember, ''));
if (isNaN(amount)) return message.reply('Please define an amount.');
let targetBalance = await db.fetch(`userBalance_${targetMember.id}`);
let selfBalance = await db.fetch(`userBalance_${message.author.id}`);
if (targetBalance === null) {
db.set(`userBalance_${targetMember.id}`, 0);
targetBalance = 0;
}
if (selfBalance === null) {
db.set(`userBalance_${message.author.id}`, 0);
selfBalance = 0;
}
if (amount > selfBalance) return message.reply('Sorry you don\'t have enough money.');
db.add(`userBalance_${targetMember.id}`, amount);
db.subtract(`userBalance_${message.author.id}`, amount);
message.reply(`Successfully transfered $${amount} to ${targetMember.user}`);
};
exports.conf = {
aliases: ['transfer'],
guildOnly: false,
};
exports.help = {
name: 'pay',
description: 'You can pay others!',
usage: 'pay [@user] [interger]',
category: '- Economy Commands',
};

View file

@ -26,4 +26,29 @@ const quote = sequelize.define('quotes', {
})
module.exports = quote
const pendingQuote = sequelize.define('pending-quotes', {
id: {
type: Sequelize.INTEGER,
autoIncrement: true,
primaryKey: true
},
author: {
type: Sequelize.STRING,
allowNull: false
},
authorImage: {
type: Sequelize.STRING,
allowNull: false
},
quote: {
type: Sequelize.TEXT,
allowNull: false
},
year: {
type: Sequelize.STRING,
allowNull: false
}
})
module.exports = { quote, pendingQuote };

View file

@ -20,6 +20,7 @@
"dependencies": {
"blessed": "^0.1.81",
"colors": "^1.3.0",
"cors": "^2.8.5",
"discord.js": "^13.0.1",
"dotenv": "^16.3.1",
"eslint": "^7.1.0",

View file

@ -92,10 +92,8 @@ const activities = [
'GNU\'s NOT UNIX!',
'Linux, but actually GNU/Linux',
'Praise RMS! (dont)',
'Praying to St IGNUcius',
'Debloating my ThinkPad',
'Turbotastic!',
'Goddamn Idiotic Truckload of Windows',
`Now running on Discord.JS ${version}!`
];

View file

@ -1,4 +1,4 @@
{
"abVersion": "2.13.0 Beta",
"prefix": "ab:"
"prefix": "abb:"
}

View file

@ -1,9 +1,13 @@
const quoteDB = require("./models/quote");
const { quote, pendingQuote } = require("./models/quote");
const guildDB = require ('./models/guild-settings');
quoteDB.sync({alter: true}).then(() => {
quote.sync({alter: true}).then(() => {
console.log('Quote database synced!')
});
pendingQuote.sync({alter: true}).then(() => {
console.log('Pending Quote database synced!')
});
guildDB.sync({alter: true}).then(() => {
console.log('Guild database synced!')
});

24
web/.gitignore vendored Normal file
View file

@ -0,0 +1,24 @@
# build output
dist/
# generated types
.astro/
# dependencies
node_modules/
# logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# environment variables
.env
.env.production
# macOS-specific files
.DS_Store
# jetbrains setting folder
.idea/

4
web/.vscode/extensions.json vendored Normal file
View file

@ -0,0 +1,4 @@
{
"recommendations": ["astro-build.astro-vscode"],
"unwantedRecommendations": []
}

11
web/.vscode/launch.json vendored Normal file
View file

@ -0,0 +1,11 @@
{
"version": "0.2.0",
"configurations": [
{
"command": "./node_modules/.bin/astro dev",
"name": "Development server",
"request": "launch",
"type": "node-terminal"
}
]
}

9
web/astro.config.mjs Normal file
View file

@ -0,0 +1,9 @@
// @ts-check
import { defineConfig } from 'astro/config';
import react from '@astrojs/react';
// https://astro.build/config
export default defineConfig({
integrations: [react()]
});

BIN
web/bun.lockb Normal file

Binary file not shown.

19
web/package.json Normal file
View file

@ -0,0 +1,19 @@
{
"name": "aleebot-web",
"type": "module",
"version": "0.0.1",
"scripts": {
"dev": "astro dev",
"build": "astro build",
"preview": "astro preview",
"astro": "astro"
},
"dependencies": {
"@astrojs/react": "^4.1.3",
"@types/react": "^19.0.4",
"@types/react-dom": "^19.0.2",
"astro": "^5.1.5",
"react": "^19.0.0",
"react-dom": "^19.0.0"
}
}

9
web/public/favicon.svg Normal file
View file

@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 128 128">
<path d="M50.4 78.5a75.1 75.1 0 0 0-28.5 6.9l24.2-65.7c.7-2 1.9-3.2 3.4-3.2h29c1.5 0 2.7 1.2 3.4 3.2l24.2 65.7s-11.6-7-28.5-7L67 45.5c-.4-1.7-1.6-2.8-2.9-2.8-1.3 0-2.5 1.1-2.9 2.7L50.4 78.5Zm-1.1 28.2Zm-4.2-20.2c-2 6.6-.6 15.8 4.2 20.2a17.5 17.5 0 0 1 .2-.7 5.5 5.5 0 0 1 5.7-4.5c2.8.1 4.3 1.5 4.7 4.7.2 1.1.2 2.3.2 3.5v.4c0 2.7.7 5.2 2.2 7.4a13 13 0 0 0 5.7 4.9v-.3l-.2-.3c-1.8-5.6-.5-9.5 4.4-12.8l1.5-1a73 73 0 0 0 3.2-2.2 16 16 0 0 0 6.8-11.4c.3-2 .1-4-.6-6l-.8.6-1.6 1a37 37 0 0 1-22.4 2.7c-5-.7-9.7-2-13.2-6.2Z" />
<style>
path { fill: #000; }
@media (prefers-color-scheme: dark) {
path { fill: #FFF; }
}
</style>
</svg>

After

Width:  |  Height:  |  Size: 749 B

View file

@ -0,0 +1,86 @@
import { useState, useEffect } from 'react';
import '../styles/Quote.css'
export function PendingQuotes() {
const [quotes, setQuotes] = useState([]);
const fetchQuotes = async () => {
try {
const response = await fetch('http://localhost:3000/api/pending-quotes');
const data = await response.json();
setQuotes(data);
} catch (error) {
console.error('Failed to fetch quotes:', error);
}
};
useEffect(() => {
fetchQuotes();
}, []);
const approveQuote = async (id) => {
try {
const response = await fetch('http://localhost:3000/api/approve-quote', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ id }),
});
if (response.ok) {
fetchQuotes(); // Refresh the listing after approving the quote
} else {
console.error('Failed to approve quote');
}
} catch (error) {
console.error('Error approving quote:', error);
}
};
const rejectQuote = async (id) => {
try {
const response = await fetch('http://localhost:3000/api/reject-quote', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ id }),
});
if (response.ok) {
fetchQuotes(); // Refresh the listing after approving the quote
} else {
console.error('Failed to reject quote');
}
} catch (error) {
console.error('Error rejecting quote:', error);
}
};
return (
<div>
<h1>Pending Quotes</h1>
{quotes.length > 0 ? (
<ul className="quoteList">
{quotes.map((quote) => (
<li key={quote.id} className="quoteList">
<div className="quote">
<div className="author">
<img src={quote.authorImage} alt="No Profile" width="50" height="50"/>
<h1 className="quoteAuthor">{quote.author}</h1>
</div>
<p className="quoteText">{quote.quote}</p>
<small>- {quote.year}</small>
</div>
<button onClick={() => approveQuote(quote.id)}>Approve</button>
<button onClick={() => rejectQuote(quote.id)}>Reject</button>
</li>
))}
</ul>
) : (
<p>No pending quotes available.</p>
)}
</div>
);
}

View file

@ -0,0 +1,22 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="generator" content={Astro.generator} />
<title>Astro Basics</title>
</head>
<body>
<slot />
</body>
</html>
<style>
html,
body {
margin: 0;
width: 100%;
height: 100%;
}
</style>

29
web/src/pages/index.astro Normal file
View file

@ -0,0 +1,29 @@
---
import Layout from '../layouts/Layout.astro';
import { PendingQuotes } from '../components/Quotes';
const version = await fetch('http://localhost:3000/api/version').then(res => res.json());
---
<Layout>
<div class="container">
<h1>AleeBot {version}</h1>
<PendingQuotes client:load />
</div>
</Layout>
<style>
@import url('https://fonts.googleapis.com/css2?family=Exo+2:ital,wght@0,100..900;1,100..900&display=swap');
html,
body {
margin: 0;
width: 100%;
height: 100%;
font-family: "Exo 2", sans-serif;
}
.container {
margin: 2em;
}
</style>

33
web/src/styles/Quote.css Normal file
View file

@ -0,0 +1,33 @@
.quote {
display: flex;
flex-direction: column;
background: #555555;
color: #FFFFFF;
padding: 1em;
}
ul.quoteList {
margin: 0;
padding: 0;
}
li.quoteList {
list-style-type: none;
margin-top: 1em;
margin-bottom: 1em;
}
.author {
display: flex;
flex-direction: row;
}
h1.quoteAuthor {
font-size: 1.5em;
padding: 0;
margin: 0 0 0 .5em;
}
.quoteText {
margin: .5em 0;
}

14
web/tsconfig.json Normal file
View file

@ -0,0 +1,14 @@
{
"extends": "astro/tsconfigs/strict",
"include": [
".astro/types.d.ts",
"**/*"
],
"exclude": [
"dist"
],
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "react"
}
}

View file

@ -513,6 +513,14 @@ cookie@0.5.0:
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b"
integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==
cors@^2.8.5:
version "2.8.5"
resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29"
integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==
dependencies:
object-assign "^4"
vary "^1"
cross-spawn@^7.0.2:
version "7.0.3"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
@ -1507,7 +1515,7 @@ npmlog@^6.0.0:
gauge "^4.0.3"
set-blocking "^2.0.0"
object-assign@^4.1.1:
object-assign@^4, object-assign@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
@ -2100,7 +2108,7 @@ validator@^13.7.0:
resolved "https://registry.yarnpkg.com/validator/-/validator-13.9.0.tgz#33e7b85b604f3bbce9bb1a05d5c3e22e1c2ff855"
integrity sha512-B+dGG8U3fdtM0/aNK4/X8CXq/EcxU2WPrPEkJGslb47qyHsxmbggTWK0yEA4qnYVNF+nxNlN88o14hIcPmSIEA==
vary@~1.1.2:
vary@^1, vary@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==