From f5de90ba89146008af78c16e798e216efccf0c50 Mon Sep 17 00:00:00 2001
From: Andrew Lee <andrew@alee14.me>
Date: Sat, 11 Jan 2025 11:55:18 -0500
Subject: Ability to add quotes, web interface, pending quotes

---
 web/src/components/Quotes.jsx | 86 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 86 insertions(+)
 create mode 100644 web/src/components/Quotes.jsx

(limited to 'web/src/components/Quotes.jsx')

diff --git a/web/src/components/Quotes.jsx b/web/src/components/Quotes.jsx
new file mode 100644
index 0000000..1d563e7
--- /dev/null
+++ b/web/src/components/Quotes.jsx
@@ -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>
+    );
+}
-- 
cgit v1.2.3


From 7e65ae0e135098acad76b8081f34478b4efc077f Mon Sep 17 00:00:00 2001
From: Andrew Lee <andrew@alee14.me>
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

(limited to 'web/src/components/Quotes.jsx')

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() {
                                 </div>
                                 <p className="quoteText">{quote.quote}</p>
                                 <small>- {quote.year}</small>
+                                <small>Submitted by {quote.submitterAuthor} ({quote.submitterID})</small>
                             </div>
                             <button onClick={() => approveQuote(quote.id)}>Approve</button>
                             <button onClick={() => rejectQuote(quote.id)}>Reject</button>
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 @@
 		<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>
+		<title>AleeBot Web Interface</title>
 	</head>
 	<body>
 		<slot />
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());
 ---
 
 <Layout>
 	<div class="container">
-		<h1>AleeBot {version}</h1>
+		<h1 id="version">AleeBot</h1>
 		<PendingQuotes client:load />
 	</div>
 </Layout>
@@ -27,3 +26,19 @@ const version = await fetch('http://localhost:3000/api/version').then(res => res
 	}
 
 </style>
+
+<script>
+	document.addEventListener('DOMContentLoaded', async () => {
+		try {
+			const version = await fetch('http://localhost:3000/api/version').then((res) => res.json());
+			const versionElement = document.getElementById('version');
+			if (versionElement) {
+				versionElement.textContent = `AleeBot ${version}`;
+			} else {
+				console.error('Element with ID "version" not found.');
+			}
+		} catch (e) {
+			console.error('Failed to fetch version:', e);
+		}
+	});
+</script>
-- 
cgit v1.2.3


From 5777f96394444dab18a81d6f085ac81df3e62008 Mon Sep 17 00:00:00 2001
From: Andrew Lee <andrew@alee14.me>
Date: Tue, 25 Feb 2025 23:13:39 -0500
Subject: 2.13 Release (finally); Added more API entries; Proper logging

---
 api/server.js                 |  51 +++++++++++++++++++-
 bot_discord.js                | 108 ++++++++++++++++++++++++------------------
 commands/addquote.js          |   4 +-
 commands/quote.js             |   3 +-
 commands/setlogchannel.js     |  25 ++++++----
 models/guild-settings.js      |  25 ++++++----
 package.json                  |   2 +-
 storage/settings.json         |   2 +-
 sync-database.js              |   4 +-
 web/astro.config.mjs          |  10 +++-
 web/src/components/Quotes.jsx |   7 +--
 web/src/pages/index.astro     |   3 +-
 12 files changed, 165 insertions(+), 79 deletions(-)

(limited to 'web/src/components/Quotes.jsx')

diff --git a/api/server.js b/api/server.js
index 2a2afad..acfe528 100644
--- a/api/server.js
+++ b/api/server.js
@@ -5,7 +5,7 @@ require('dotenv').config()
 
 const app = express();
 
-const createServer = () => {
+const apiServer = (client) => {
     app.use(cors()); // Allow cross-origin requests
     app.use(express.json());
 
@@ -17,10 +17,57 @@ const createServer = () => {
 
     });
 
+    app.get('/api/uptime', (req, res) => {
+        res.json(client.uptime);
+    });
+
+    app.get('/api/servers', (req, res) => {
+        const guildsInfo = [];
+
+        if (client.guilds.cache.size === 0) {
+            res.json({
+                message: 'No servers found'
+            })
+        } else {
+            client.guilds.cache.forEach((guild) => {
+                const guildInfo = {
+                    name: guild.name,
+                    members: guild.memberCount,
+                    id: guild.id
+                };
+                guildsInfo.push(guildInfo);
+            });
+        }
+
+        res.json(guildsInfo);
+
+    });
+
+    app.post('/api/leave', (req, res) => {
+        const { id } = req.body;
+        let guild = client.guilds.cache.get(id);
+
+        try {
+            guild.leave().then(guild => {
+                res.json({
+                    guild: guild.name,
+                    left: true
+                })
+            });
+
+        } catch (error) {
+            console.error('Error leaving server:', error);
+            res.status(500).res.json({
+                guild: guild.name,
+                left: false
+            })
+        }
+    });
+
     // Start the server
     app.listen(process.env.port, () => {
         console.log(`Server is running on http://localhost:${process.env.port}`);
     });
 };
 
-module.exports = createServer;
+module.exports = apiServer;
diff --git a/bot_discord.js b/bot_discord.js
index 5a38940..1a87074 100644
--- a/bot_discord.js
+++ b/bot_discord.js
@@ -33,12 +33,15 @@ const colors = require('colors');
 //const i18next = require('i18next');
 const settings = require('./storage/settings.json');
 const { activity } = require('./storage/activities');
-const createServer = require("./api/server");
+const apiServer = require("./api/server");
 const active = new Map();
 let autoRole = true;
 let readyEmbedMessage = true;
+
+const { guildSettings } = require('./models/guild-settings');
+
 const ownerID = '242775871059001344';
-let logChannel = '318874545593384970';
+//let logChannel = '318874545593384970';
 let statusChannelID = '606602551634296968';
 let serverWhitelist = "243022206437687296";
 let roleWhitelist = "657426918416580614";
@@ -218,7 +221,7 @@ client.on('ready', async () => {
 
 	botPresence();
 
-	createServer();
+	apiServer(client);
 
 	setInterval(function() {
 	botPresence();
@@ -238,8 +241,10 @@ client.on('ready', async () => {
 	rl.prompt();
 });
 
-client.on('guildMemberAdd', (member) => {
-	if (member.guild.id !== serverWhitelist) return;
+client.on('guildMemberAdd', async (member) => {
+	const guildSetting = await guildSettings.findOne({ where: { guildID: member.guild.id } });
+	if (!guildSetting || !guildSetting.logChannelID) return;
+
 	const logEmbed = new Discord.MessageEmbed()
 		.setAuthor('AleeBot Logging', client.user.avatarURL())
 		.setDescription(`A user has joined this server!`)
@@ -249,7 +254,7 @@ client.on('guildMemberAdd', (member) => {
 		.setColor('#4bff31')
 		.setTimestamp();
 
-	let guildMember = client.channels.cache.get(logChannel);
+	let guildMember = client.channels.cache.get(guildSetting.logChannelID);
 	if (!guildMember) return;
 
 	guildMember.send({ embeds: [logEmbed]});
@@ -261,8 +266,10 @@ client.on('guildMemberAdd', (member) => {
 	}
 });
 
-client.on('guildMemberRemove', (member) => {
-	if (member.guild.id !== serverWhitelist) return;
+client.on('guildMemberRemove', async (member) => {
+	const guildSetting = await guildSettings.findOne({ where: { guildID: member.guild.id } });
+	if (!guildSetting || !guildSetting.logChannelID) return;
+
 	const logEmbed = new Discord.MessageEmbed()
 		.setAuthor('AleeBot Logging', client.user.avatarURL())
 		.setDescription(`A user has left this server!`)
@@ -271,7 +278,7 @@ client.on('guildMemberRemove', (member) => {
 		.setColor('#ec2727')
 		.setTimestamp();
 
-	let guildMember = client.channels.cache.get(logChannel);
+	let guildMember = client.channels.cache.get(guildSetting.logChannelID);
 	if (!guildMember) return;
 
 	guildMember.send({ embeds: [logEmbed]});
@@ -279,7 +286,10 @@ client.on('guildMemberRemove', (member) => {
 
 
 client.on('messageUpdate', async (oldMessage, newMessage) => {
-	if (!oldMessage.guild || oldMessage.guild.id !== serverWhitelist) return;
+	const guildSetting = await guildSettings.findOne({ where: { guildID: oldMessage.guild.id } });
+	if (!oldMessage.guild || !guildSetting || !guildSetting.logChannelID) return;
+
+	//if (!oldMessage.guild || oldMessage.guild.id !== serverWhitelist) return;
 	if (oldMessage.content === newMessage.content) {
 		return;
 	}
@@ -292,14 +302,18 @@ client.on('messageUpdate', async (oldMessage, newMessage) => {
 		.setTimestamp()
 		.setFooter(`Author ID: ${oldMessage.author.id}`);
 
-	let editMessage = client.channels.cache.get(logChannel);
+	let editMessage = client.channels.cache.get(guildSetting.logChannelID);
 	if (!editMessage) return;
 
 	editMessage.send({ embeds: [logEmbed]});
 });
 
-client.on('messageDelete', (message) => {
-	if (message.guild.id !== serverWhitelist) return;
+client.on('messageDelete', async (message) => {
+	if (!message.content) return;
+
+	const guildSetting = await guildSettings.findOne({ where: { guildID: message.guild.id } });
+	if (!guildSetting || !guildSetting.logChannelID) return;
+
 	const logEmbed = new Discord.MessageEmbed()
 		.setAuthor('AleeBot Logging', client.user.avatarURL())
 		.setDescription(`A message from ${message.author.username} was deleted in <#${message.channel.id}>`)
@@ -308,43 +322,47 @@ client.on('messageDelete', (message) => {
 		.setTimestamp()
 		.setFooter(`Author ID: ${message.author.id}`);
 
-	let deleteMessage = client.channels.cache.get(logChannel);
+	let deleteMessage = client.channels.cache.get(guildSetting.logChannelID);
 	if (!deleteMessage) return;
 
 	deleteMessage.send({ embeds: [logEmbed]});
 });
 
-client.on('guildBanAdd', (guild, user) => {
-	if (guild.id !== serverWhitelist) return;
-	const logEmbed = new Discord.MessageEmbed()
-		.setAuthor('AleeBot Logging', client.user.avatarURL())
-		.setDescription(`This user got banned from ${guild.name}`)
-		.addField('User:', `${user.tag}`)
-		.addField('User ID:', `${user.id}`)
-		.setColor('#ff021b')
-		.setTimestamp();
-
-	let banMessage = client.channels.cache.get(logChannel);
-	if (!banMessage) return;
-
-	banMessage.send({ embeds: [logEmbed]});
-});
-
-client.on('guildBanRemove', (guild, user) => {
-	if (guild.id !== serverWhitelist) return;
-	const logEmbed = new Discord.MessageEmbed()
-		.setAuthor('AleeBot Logging', client.user.avatarURL())
-		.setDescription(`This user got unbanned from ${guild.name}`)
-		.addField('User:', `${user.tag}`)
-		.addField('User ID:', `${user.id}`)
-		.setColor('#ff021b')
-		.setTimestamp();
-
-	let banMessage = client.channels.cache.get(logChannel);
-	if (!banMessage) return;
-
-	banMessage.send({ embeds: [logEmbed]});
-});
+// client.on('guildBanAdd', async (guild, user) => {
+// 	const guildSetting = await guildSettings.findOne({ where: { guildID: guild.id } });
+// 	if (!guildSetting || !guildSetting.logChannelID) return;
+//
+// 	const logEmbed = new Discord.MessageEmbed()
+// 		.setAuthor('AleeBot Logging', client.user.avatarURL())
+// 		.setDescription(`This user got banned from ${guild.name}`)
+// 		.addField('User:', `${user.tag}`)
+// 		.addField('User ID:', `${user.id}`)
+// 		.setColor('#ff021b')
+// 		.setTimestamp();
+//
+// 	let banMessage = client.channels.cache.get(guildSetting.logChannelID);
+// 	if (!banMessage) return;
+//
+// 	banMessage.send({ embeds: [logEmbed]});
+// });
+//
+// client.on('guildBanRemove', async (guild, user) => {
+// 	const guildSetting = await guildSettings.findOne({ where: { guildID: guild.id } });
+// 	if (!guildSetting || !guildSetting.logChannelID) return;
+//
+// 	const logEmbed = new Discord.MessageEmbed()
+// 		.setAuthor('AleeBot Logging', client.user.avatarURL())
+// 		.setDescription(`This user got unbanned from ${guild.name}`)
+// 		.addField('User:', `${user.tag}`)
+// 		.addField('User ID:', `${user.id}`)
+// 		.setColor('#ff021b')
+// 		.setTimestamp();
+//
+// 	let banMessage = client.channels.cache.get(guildSetting.logChannelID);
+// 	if (!banMessage) return;
+//
+// 	banMessage.send({ embeds: [logEmbed]});
+// });
 
 client.on('guildCreate', (guild) => {
 	log(`[i] New guild joined: ${guild.name} (${guild.id}). This guild has ${guild.memberCount} members!`.blue);
diff --git a/commands/addquote.js b/commands/addquote.js
index cedee4d..6eae015 100644
--- a/commands/addquote.js
+++ b/commands/addquote.js
@@ -85,7 +85,7 @@ module.exports.run = async (client, message) => {
 						return;
 					}
 				} else if (msg.content.startsWith('http') && (msg.content.endsWith('.jpg') || msg.content.endsWith('.jpeg')  || msg.content.endsWith('.png'))) {
-					newAuthorImage = message.content;
+					newAuthorImage = msg.content;
 				} else {
 					await dmChannel.send('Invalid input. Please provide an image URL or attach an image file.');
 					collector.stop();
@@ -157,7 +157,7 @@ module.exports.run = async (client, message) => {
 									await dmChannel.send('Invalid file type. Please attach a .jpg or .png image.');
 								}
 							} else if (imageResponse.first().content.startsWith('http') && (imageResponse.first().content.endsWith('.jpg') || imageResponse.first().content.endsWith('.jpeg')  || imageResponse.first().content.endsWith('.png'))) {
-								newAuthorImage = message.content;
+								newAuthorImage = imageResponse.first().content;
 							} else {
 								await dmChannel.send('Invalid input. Please provide an image URL or attach an image file.');
 							}
diff --git a/commands/quote.js b/commands/quote.js
index 02699cd..f19675f 100644
--- a/commands/quote.js
+++ b/commands/quote.js
@@ -30,13 +30,12 @@ module.exports.run = async (client, message, args) => {
 
 	const quote = await quoteDB.findOne({ where: { id: quoteID } })
 
-
 	if (quote) {
 		const quoteEmbed = new MessageEmbed()
 			.setAuthor({ name: quote.author, iconURL: quote.authorImage })
 			.setDescription(quote.quote)
 			.setColor('#1fd619')
-			.setFooter('- ' + quote.year);
+			.setFooter(`- ${quote.year}\nSubmitted by ${quote.submitter}`);
 
 		await message.reply({ embeds: [quoteEmbed] })
 	} else {
diff --git a/commands/setlogchannel.js b/commands/setlogchannel.js
index c04e926..d989f8b 100644
--- a/commands/setlogchannel.js
+++ b/commands/setlogchannel.js
@@ -17,21 +17,30 @@
  *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
  *
  * *************************************/
-const guildDB = require ('../models/guild-settings')
-module.exports.run = async (client, message) => {
+const { guildSettings } = require ('../models/guild-settings')
+module.exports.run = async (client, message, args) => {
     //This will be replaced in the future possibly
     if (!message.member.permissions.has('MANAGE_GUILD')) return message.reply('It looks like that you can\'t manage this server.');
-    const channel = await message.mentions.channels.first().id;
-    const [ guild ] = await guildDB.findOrCreate({ where: { id: message.guild.id } } )
+
+    let channel;
+
+    if (message.mentions.channels.first()) {
+        channel = message.mentions.channels.first().id;
+    } else if (args[0] && message.guild.channels.cache.has(args[0])) {
+        channel = args[0];
+    } else {
+        return await message.reply('Please enter a valid channel ID.')
+    }
+
+    const [ guild ] = await guildSettings.findOrCreate({ where: { guildID: message.guild.id } } )
 
     if (!channel) {
         message.reply('No channel has been set, disabling the logging channel feature...');
-        await guild.update({ channelId: null } );
+        await guild.update({ logChannelID: null } );
     } else {
-        await guild.update({ channelId: message.guild.id } )   ;
+        await guild.update({ logChannelID: channel } );
+        await message.reply(`Logging channel has been set to <#${channel}>`);
     }
-
-    await message.reply(`Logging channel has been set to <#${channel}>`);
 };
 
 exports.conf = {
diff --git a/models/guild-settings.js b/models/guild-settings.js
index 2e4f8ef..8cc7478 100644
--- a/models/guild-settings.js
+++ b/models/guild-settings.js
@@ -3,22 +3,27 @@ const sequelize = require('../utils/sequelize');
 
 const guildSettings = sequelize.define('guild-settings', {
     id: {
-        type: Sequelize.STRING,
-        primaryKey: true
+        type: Sequelize.INTEGER,
+        primaryKey: true,
+        autoIncrement: true,
     },
-    logChannelID: {
+    guildID: {
         type: Sequelize.STRING,
-        allowNull: true
+        allowNull: false
     },
-    autoRoleToggle: {
-        type: Sequelize.BOOLEAN,
-        allowNull: true
-    },
-    autoRoleID: {
+    logChannelID: {
         type: Sequelize.STRING,
         allowNull: true
     }
+    // qotdChannelID: {
+    //     type: Sequelize.STRING,
+    //     allowNull: true
+    // },
+    // qotdToggle: {
+    //     type: Sequelize.BOOLEAN,
+    //     allowNull: true
+    // }
 
 })
 
-module.exports = guildSettings
+module.exports = { guildSettings }
diff --git a/package.json b/package.json
index b300082..404b4a5 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "aleebot",
-  "version": "2.13.0b",
+  "version": "2.13.0",
   "description": "A chat bot for discord written in discord.js.",
   "main": "bot_discord.js",
   "scripts": {
diff --git a/storage/settings.json b/storage/settings.json
index b53fd84..4a6911c 100644
--- a/storage/settings.json
+++ b/storage/settings.json
@@ -1,4 +1,4 @@
 {
-  "abVersion": "2.13.0 Beta",
+  "abVersion": "2.13.0",
   "prefix": "ab:"
 }
diff --git a/sync-database.js b/sync-database.js
index f7b9211..7a9271c 100644
--- a/sync-database.js
+++ b/sync-database.js
@@ -1,5 +1,5 @@
 const { quote, pendingQuote } = require("./models/quote");
-const guildDB = require ('./models/guild-settings');
+const { guildSettings } = require ('./models/guild-settings');
 quote.sync({alter: true}).then(() => {
     console.log('Quote database synced!')
 });
@@ -8,6 +8,6 @@ pendingQuote.sync({alter: true}).then(() => {
     console.log('Pending Quote database synced!')
 });
 
-guildDB.sync({alter: true}).then(() => {
+guildSettings.sync({alter: true}).then(() => {
     console.log('Guild database synced!')
 });
diff --git a/web/astro.config.mjs b/web/astro.config.mjs
index c0fd9ad..d32e23b 100644
--- a/web/astro.config.mjs
+++ b/web/astro.config.mjs
@@ -1,5 +1,5 @@
 // @ts-check
-import { defineConfig } from 'astro/config';
+import { defineConfig, envField } from 'astro/config';
 
 import react from '@astrojs/react';
 
@@ -11,5 +11,11 @@ export default defineConfig({
 
   adapter: node({
     mode: 'standalone'
-  })
+  }),
+
+  env: {
+    schema: {
+      API_URL: envField.string({ context: 'client', access: 'public'  }),
+    }
+  }
 });
diff --git a/web/src/components/Quotes.jsx b/web/src/components/Quotes.jsx
index 831408d..1eb258a 100644
--- a/web/src/components/Quotes.jsx
+++ b/web/src/components/Quotes.jsx
@@ -1,12 +1,13 @@
 import { useState, useEffect } from 'react';
 import '../styles/Quote.css'
+import { API_URL } from "astro:env/client";
 
 export function PendingQuotes() {
     const [quotes, setQuotes] = useState([]);
 
     const fetchQuotes = async () => {
         try {
-            const response = await fetch('http://localhost:3000/api/pending-quotes');
+            const response = await fetch(`${API_URL}/api/pending-quotes`);
             const data = await response.json();
             setQuotes(data);
         } catch (error) {
@@ -20,7 +21,7 @@ export function PendingQuotes() {
 
     const approveQuote = async (id) => {
         try {
-            const response = await fetch('http://localhost:3000/api/approve-quote', {
+            const response = await fetch(`${API_URL}/api/approve-quote`, {
                 method: 'POST',
                 headers: {
                     'Content-Type': 'application/json',
@@ -40,7 +41,7 @@ export function PendingQuotes() {
 
     const rejectQuote = async (id) => {
         try {
-            const response = await fetch('http://localhost:3000/api/reject-quote', {
+            const response = await fetch(`${API_URL}/api/reject-quote`, {
                 method: 'POST',
                 headers: {
                     'Content-Type': 'application/json',
diff --git a/web/src/pages/index.astro b/web/src/pages/index.astro
index 8136402..f1dc6e7 100644
--- a/web/src/pages/index.astro
+++ b/web/src/pages/index.astro
@@ -28,9 +28,10 @@ import { PendingQuotes } from '../components/Quotes';
 </style>
 
 <script>
+	import { API_URL } from "astro:env/client"
 	document.addEventListener('DOMContentLoaded', async () => {
 		try {
-			const version = await fetch('http://localhost:3000/api/version').then((res) => res.json());
+			const version = await fetch(`${API_URL}/api/version`).then((res) => res.json());
 			const versionElement = document.getElementById('version');
 			if (versionElement) {
 				versionElement.textContent = `AleeBot ${version}`;
-- 
cgit v1.2.3

