ui changes
This commit is contained in:
38
backend/db.js
Normal file
38
backend/db.js
Normal file
@@ -0,0 +1,38 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const CryptoJS = require('crypto-js');
|
||||
|
||||
const encryptionKey = process.env.ENCRYPTION_KEY;
|
||||
const dbPath = path.join(__dirname, 'db.json');
|
||||
|
||||
const readDb = () => {
|
||||
try {
|
||||
const data = fs.readFileSync(dbPath, 'utf8');
|
||||
if (data) {
|
||||
const bytes = CryptoJS.AES.decrypt(data, encryptionKey);
|
||||
const decryptedData = bytes.toString(CryptoJS.enc.Utf8);
|
||||
if (decryptedData) {
|
||||
return JSON.parse(decryptedData);
|
||||
}
|
||||
}
|
||||
return {};
|
||||
} catch (error) {
|
||||
if (error.code === 'ENOENT') {
|
||||
return {};
|
||||
}
|
||||
console.error('Error reading or decrypting db.json:', error);
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
const writeDb = (db) => {
|
||||
try {
|
||||
const data = JSON.stringify(db, null, 2);
|
||||
const encryptedData = CryptoJS.AES.encrypt(data, encryptionKey).toString();
|
||||
fs.writeFileSync(dbPath, encryptedData, 'utf8');
|
||||
} catch (error) {
|
||||
console.error('Error encrypting or writing to db.json:', error);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = { readDb, writeDb, dbPath };
|
||||
140
backend/index.js
140
backend/index.js
@@ -1,4 +1,4 @@
|
||||
require('dotenv').config();
|
||||
require('dotenv').config({ path: __dirname + '/.env' });
|
||||
const express = require('express');
|
||||
const cors = require('cors');
|
||||
|
||||
@@ -52,56 +52,32 @@ app.get('/auth/discord/callback', async (req, res) => {
|
||||
const adminGuilds = guildsResponse.data.filter(guild => (guild.permissions & 0x8) === 0x8);
|
||||
|
||||
const user = userResponse.data;
|
||||
fs.readFile('db.json', 'utf8', (err, data) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
user.theme = 'light';
|
||||
} else {
|
||||
const db = JSON.parse(data);
|
||||
user.theme = db.users && db.users[user.id] ? db.users[user.id].theme : 'light';
|
||||
}
|
||||
const guilds = adminGuilds;
|
||||
res.redirect(`http://localhost:3000/dashboard?user=${encodeURIComponent(JSON.stringify(user))}&guilds=${encodeURIComponent(JSON.stringify(guilds))}`);
|
||||
});
|
||||
const db = readDb();
|
||||
user.theme = db.users && db.users[user.id] ? db.users[user.id].theme : 'light';
|
||||
const guilds = adminGuilds;
|
||||
res.redirect(`http://localhost:3000/dashboard?user=${encodeURIComponent(JSON.stringify(user))}&guilds=${encodeURIComponent(JSON.stringify(guilds))}`);
|
||||
} catch (error) {
|
||||
console.error('Error during Discord OAuth2 callback:', error);
|
||||
res.status(500).send('Internal Server Error');
|
||||
}
|
||||
});
|
||||
|
||||
const fs = require('fs');
|
||||
const { readDb, writeDb } = require('./db');
|
||||
|
||||
app.get('/api/servers/:guildId/settings', (req, res) => {
|
||||
const { guildId } = req.params;
|
||||
fs.readFile('db.json', 'utf8', (err, data) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return res.status(500).send('Internal Server Error');
|
||||
}
|
||||
const db = JSON.parse(data);
|
||||
const settings = db[guildId] || { pingCommand: false };
|
||||
res.json(settings);
|
||||
});
|
||||
const db = readDb();
|
||||
const settings = db[guildId] || { pingCommand: false };
|
||||
res.json(settings);
|
||||
});
|
||||
|
||||
app.post('/api/servers/:guildId/settings', (req, res) => {
|
||||
const { guildId } = req.params;
|
||||
const { pingCommand } = req.body;
|
||||
fs.readFile('db.json', 'utf8', (err, data) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return res.status(500).send('Internal Server Error');
|
||||
}
|
||||
const db = JSON.parse(data);
|
||||
db[guildId] = { pingCommand };
|
||||
fs.writeFile('db.json', JSON.stringify(db, null, 2), (err) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return res.status(500).send('Internal Server Error');
|
||||
}
|
||||
res.json({ success: true });
|
||||
});
|
||||
});
|
||||
const db = readDb();
|
||||
db[guildId] = { pingCommand };
|
||||
writeDb(db);
|
||||
res.json({ success: true });
|
||||
});
|
||||
|
||||
app.get('/api/servers/:guildId/bot-status', (req, res) => {
|
||||
@@ -120,27 +96,77 @@ app.get('/api/client-id', (req, res) => {
|
||||
|
||||
app.post('/api/user/theme', (req, res) => {
|
||||
const { userId, theme } = req.body;
|
||||
fs.readFile('db.json', 'utf8', (err, data) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return res.status(500).send('Internal Server Error');
|
||||
}
|
||||
const db = JSON.parse(data);
|
||||
if (!db.users) {
|
||||
db.users = {};
|
||||
}
|
||||
if (!db.users[userId]) {
|
||||
db.users[userId] = {};
|
||||
}
|
||||
db.users[userId].theme = theme;
|
||||
fs.writeFile('db.json', JSON.stringify(db, null, 2), (err) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return res.status(500).send('Internal Server Error');
|
||||
}
|
||||
const db = readDb();
|
||||
if (!db.users) {
|
||||
db.users = {};
|
||||
}
|
||||
if (!db.users[userId]) {
|
||||
db.users[userId] = {};
|
||||
}
|
||||
db.users[userId].theme = theme;
|
||||
writeDb(db);
|
||||
res.json({ success: true });
|
||||
});
|
||||
|
||||
app.post('/api/servers/:guildId/leave', async (req, res) => {
|
||||
const { guildId } = req.params;
|
||||
try {
|
||||
const guild = await bot.client.guilds.fetch(guildId);
|
||||
if (guild) {
|
||||
await guild.leave();
|
||||
res.json({ success: true });
|
||||
});
|
||||
});
|
||||
} else {
|
||||
res.status(404).json({ success: false, message: 'Bot is not in the specified server' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error leaving server:', error);
|
||||
res.status(500).json({ success: false, message: 'Internal Server Error' });
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/api/servers/:guildId/channels', async (req, res) => {
|
||||
const { guildId } = req.params;
|
||||
const guild = bot.client.guilds.cache.get(guildId);
|
||||
if (!guild) {
|
||||
return res.json([]);
|
||||
}
|
||||
try {
|
||||
const channels = await guild.channels.fetch();
|
||||
const textChannels = channels.filter(channel => channel.type === 0).map(channel => ({ id: channel.id, name: channel.name }));
|
||||
res.json(textChannels);
|
||||
} catch (error) {
|
||||
console.error('Error fetching channels:', error);
|
||||
res.status(500).json({ success: false, message: 'Internal Server Error' });
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/api/servers/:guildId/welcome-leave-settings', (req, res) => {
|
||||
const { guildId } = req.params;
|
||||
const db = readDb();
|
||||
const settings = db[`${guildId}_welcome_leave`] || {
|
||||
welcome: {
|
||||
enabled: false,
|
||||
channel: '',
|
||||
message: 'Welcome to the server, {user}!',
|
||||
customMessage: '',
|
||||
},
|
||||
leave: {
|
||||
enabled: false,
|
||||
channel: '',
|
||||
message: '{user} has left the server.',
|
||||
customMessage: '',
|
||||
},
|
||||
};
|
||||
res.json(settings);
|
||||
});
|
||||
|
||||
app.post('/api/servers/:guildId/welcome-leave-settings', (req, res) => {
|
||||
const { guildId } = req.params;
|
||||
const newSettings = req.body;
|
||||
const db = readDb();
|
||||
db[`${guildId}_welcome_leave`] = newSettings;
|
||||
writeDb(db);
|
||||
res.json({ success: true });
|
||||
});
|
||||
|
||||
app.get('/', (req, res) => {
|
||||
|
||||
304
backend/package-lock.json
generated
304
backend/package-lock.json
generated
@@ -11,7 +11,7 @@
|
||||
"dependencies": {
|
||||
"axios": "^1.7.2",
|
||||
"cors": "^2.8.5",
|
||||
"discord.js": "^14.15.3",
|
||||
"crypto-js": "^4.2.0",
|
||||
"dotenv": "^16.4.5",
|
||||
"express": "^4.19.2"
|
||||
},
|
||||
@@ -19,194 +19,6 @@
|
||||
"nodemon": "^3.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@discordjs/builders": {
|
||||
"version": "1.11.3",
|
||||
"resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.11.3.tgz",
|
||||
"integrity": "sha512-p3kf5eV49CJiRTfhtutUCeivSyQ/l2JlKodW1ZquRwwvlOWmG9+6jFShX6x8rUiYhnP6wKI96rgN/SXMy5e5aw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@discordjs/formatters": "^0.6.1",
|
||||
"@discordjs/util": "^1.1.1",
|
||||
"@sapphire/shapeshift": "^4.0.0",
|
||||
"discord-api-types": "^0.38.16",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"ts-mixer": "^6.0.4",
|
||||
"tslib": "^2.6.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.11.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/discordjs/discord.js?sponsor"
|
||||
}
|
||||
},
|
||||
"node_modules/@discordjs/collection": {
|
||||
"version": "1.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-1.5.3.tgz",
|
||||
"integrity": "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=16.11.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@discordjs/formatters": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.6.1.tgz",
|
||||
"integrity": "sha512-5cnX+tASiPCqCWtFcFslxBVUaCetB0thvM/JyavhbXInP1HJIEU+Qv/zMrnuwSsX3yWH2lVXNJZeDK3EiP4HHg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"discord-api-types": "^0.38.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.11.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/discordjs/discord.js?sponsor"
|
||||
}
|
||||
},
|
||||
"node_modules/@discordjs/rest": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.6.0.tgz",
|
||||
"integrity": "sha512-RDYrhmpB7mTvmCKcpj+pc5k7POKszS4E2O9TYc+U+Y4iaCP+r910QdO43qmpOja8LRr1RJ0b3U+CqVsnPqzf4w==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@discordjs/collection": "^2.1.1",
|
||||
"@discordjs/util": "^1.1.1",
|
||||
"@sapphire/async-queue": "^1.5.3",
|
||||
"@sapphire/snowflake": "^3.5.3",
|
||||
"@vladfrangu/async_event_emitter": "^2.4.6",
|
||||
"discord-api-types": "^0.38.16",
|
||||
"magic-bytes.js": "^1.10.0",
|
||||
"tslib": "^2.6.3",
|
||||
"undici": "6.21.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/discordjs/discord.js?sponsor"
|
||||
}
|
||||
},
|
||||
"node_modules/@discordjs/rest/node_modules/@discordjs/collection": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz",
|
||||
"integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/discordjs/discord.js?sponsor"
|
||||
}
|
||||
},
|
||||
"node_modules/@discordjs/util": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.1.1.tgz",
|
||||
"integrity": "sha512-eddz6UnOBEB1oITPinyrB2Pttej49M9FZQY8NxgEvc3tq6ZICZ19m70RsmzRdDHk80O9NoYN/25AqJl8vPVf/g==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/discordjs/discord.js?sponsor"
|
||||
}
|
||||
},
|
||||
"node_modules/@discordjs/ws": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.2.3.tgz",
|
||||
"integrity": "sha512-wPlQDxEmlDg5IxhJPuxXr3Vy9AjYq5xCvFWGJyD7w7Np8ZGu+Mc+97LCoEc/+AYCo2IDpKioiH0/c/mj5ZR9Uw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@discordjs/collection": "^2.1.0",
|
||||
"@discordjs/rest": "^2.5.1",
|
||||
"@discordjs/util": "^1.1.0",
|
||||
"@sapphire/async-queue": "^1.5.2",
|
||||
"@types/ws": "^8.5.10",
|
||||
"@vladfrangu/async_event_emitter": "^2.2.4",
|
||||
"discord-api-types": "^0.38.1",
|
||||
"tslib": "^2.6.2",
|
||||
"ws": "^8.17.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.11.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/discordjs/discord.js?sponsor"
|
||||
}
|
||||
},
|
||||
"node_modules/@discordjs/ws/node_modules/@discordjs/collection": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz",
|
||||
"integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/discordjs/discord.js?sponsor"
|
||||
}
|
||||
},
|
||||
"node_modules/@sapphire/async-queue": {
|
||||
"version": "1.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.5.tgz",
|
||||
"integrity": "sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=v14.0.0",
|
||||
"npm": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@sapphire/shapeshift": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-4.0.0.tgz",
|
||||
"integrity": "sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"lodash": "^4.17.21"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=v16"
|
||||
}
|
||||
},
|
||||
"node_modules/@sapphire/snowflake": {
|
||||
"version": "3.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.3.tgz",
|
||||
"integrity": "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=v14.0.0",
|
||||
"npm": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "24.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.6.2.tgz",
|
||||
"integrity": "sha512-d2L25Y4j+W3ZlNAeMKcy7yDsK425ibcAOO2t7aPTz6gNMH0z2GThtwENCDc0d/Pw9wgyRqE5Px1wkV7naz8ang==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~7.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/ws": {
|
||||
"version": "8.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
|
||||
"integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@vladfrangu/async_event_emitter": {
|
||||
"version": "2.4.6",
|
||||
"resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.4.6.tgz",
|
||||
"integrity": "sha512-RaI5qZo6D2CVS6sTHFKg1v5Ohq/+Bo2LZ5gzUEwZ/WkHhwtGTCB/sVLw8ijOkAUxasZ+WshN/Rzj4ywsABJ5ZA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=v14.0.0",
|
||||
"npm": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/accepts": {
|
||||
"version": "1.3.8",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
|
||||
@@ -456,6 +268,12 @@
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/crypto-js": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz",
|
||||
"integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
@@ -493,42 +311,6 @@
|
||||
"npm": "1.2.8000 || >= 1.4.16"
|
||||
}
|
||||
},
|
||||
"node_modules/discord-api-types": {
|
||||
"version": "0.38.26",
|
||||
"resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.26.tgz",
|
||||
"integrity": "sha512-xpmPviHjIJ6dFu1eNwNDIGQ3N6qmPUUYFVAx/YZ64h7ZgPkTcKjnciD8bZe8Vbeji7yS5uYljyciunpq0J5NSw==",
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
"scripts/actions/documentation"
|
||||
]
|
||||
},
|
||||
"node_modules/discord.js": {
|
||||
"version": "14.22.1",
|
||||
"resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.22.1.tgz",
|
||||
"integrity": "sha512-3k+Kisd/v570Jr68A1kNs7qVhNehDwDJAPe4DZ2Syt+/zobf9zEcuYFvsfIaAOgCa0BiHMfOOKQY4eYINl0z7w==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@discordjs/builders": "^1.11.2",
|
||||
"@discordjs/collection": "1.5.3",
|
||||
"@discordjs/formatters": "^0.6.1",
|
||||
"@discordjs/rest": "^2.6.0",
|
||||
"@discordjs/util": "^1.1.1",
|
||||
"@discordjs/ws": "^1.2.3",
|
||||
"@sapphire/snowflake": "3.5.3",
|
||||
"discord-api-types": "^0.38.16",
|
||||
"fast-deep-equal": "3.1.3",
|
||||
"lodash.snakecase": "4.1.1",
|
||||
"magic-bytes.js": "^1.10.0",
|
||||
"tslib": "^2.6.3",
|
||||
"undici": "6.21.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/discordjs/discord.js?sponsor"
|
||||
}
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "16.6.1",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
|
||||
@@ -676,12 +458,6 @@
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-deep-equal": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
@@ -998,24 +774,6 @@
|
||||
"node": ">=0.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.snakecase": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz",
|
||||
"integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/magic-bytes.js": {
|
||||
"version": "1.12.1",
|
||||
"resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.12.1.tgz",
|
||||
"integrity": "sha512-ThQLOhN86ZkJ7qemtVRGYM+gRgR8GEXNli9H/PMvpnZsE44Xfh3wx9kGJaldg314v85m+bFW6WBMaVHJc/c3zA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
@@ -1554,18 +1312,6 @@
|
||||
"nodetouch": "bin/nodetouch.js"
|
||||
}
|
||||
},
|
||||
"node_modules/ts-mixer": {
|
||||
"version": "6.0.4",
|
||||
"resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz",
|
||||
"integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/type-is": {
|
||||
"version": "1.6.18",
|
||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
|
||||
@@ -1586,21 +1332,6 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/undici": {
|
||||
"version": "6.21.3",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz",
|
||||
"integrity": "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18.17"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "7.13.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.13.0.tgz",
|
||||
"integrity": "sha512-Ov2Rr9Sx+fRgagJ5AX0qvItZG/JKKoBRAVITs1zk7IqZGTJUwgUr7qoYBpWwakpWilTZFM98rG/AFRocu10iIQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/unpipe": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||
@@ -1627,27 +1358,6 @@
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.18.3",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
|
||||
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": ">=5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"dependencies": {
|
||||
"axios": "^1.7.2",
|
||||
"cors": "^2.8.5",
|
||||
"crypto-js": "^4.2.0",
|
||||
"dotenv": "^16.4.5",
|
||||
"express": "^4.19.2"
|
||||
},
|
||||
|
||||
27
checklist.md
27
checklist.md
@@ -8,6 +8,8 @@
|
||||
- [x] Set up Discord OAuth2
|
||||
- [x] Create API endpoint to get user's servers
|
||||
- [x] Store user theme preference on the backend
|
||||
- [x] Create API endpoint to make bot leave a server
|
||||
- [x] Encrypt user information in `db.json`.
|
||||
|
||||
## Frontend
|
||||
- [x] Create login page
|
||||
@@ -43,6 +45,13 @@
|
||||
- Acceptance criteria: On the server-specific settings page, the "Invite Bot" button (or the "Bot is already in this server" text) should be vertically and horizontally aligned with the main server name title for a cleaner look.
|
||||
- [x] Fix incorrect invite link on dashboard cards
|
||||
- Acceptance criteria: The invite link generated for the dashboard cards should correctly redirect to the Discord OAuth2 authorization page with the proper client ID, permissions, and scope, matching the functionality of the invite button in the server settings panel.
|
||||
- [x] ~~by default hide or gray out bot options in each server dashboard panel if the bot is not in the server. Only allow to edit the features if the bot is in the discord server.~~ (User changed their mind)
|
||||
- [x] Allow dashboard cards to be clickable even if the bot is not in the server. Inside the server settings, all commands and categories should be greyed out and not touchable. Only allow the invite button to be clicked within.
|
||||
- [x] Add a button to the server cards that allows the user to make the bot leave the server. The button should only be visible if the bot is in the server.
|
||||
- [x] In the server settings, replace the text "The bot is already in this server" with a "Leave" button.
|
||||
- [x] Add a confirmation dialog to the "Leave" buttons on both the dashboard cards and the server settings page.
|
||||
- [x] Redesign the login page to be more bubbly, centered, and eye-catching, with bigger text and responsive design.
|
||||
- [x] Make server settings panels collapsible for a cleaner mobile UI.
|
||||
|
||||
## Discord Bot
|
||||
- [x] Create a basic Discord bot
|
||||
@@ -52,3 +61,21 @@
|
||||
- [x] Implement command handler
|
||||
- [x] Implement event handler
|
||||
- [x] Set bot status on ready event
|
||||
- [x] Automatically register slash commands on server join.
|
||||
|
||||
## Features
|
||||
- [x] **Welcome/Leave Messages**
|
||||
- [x] Add "Welcome/Leave" section to server settings.
|
||||
- [x] **Welcome Messages:**
|
||||
- [x] Add toggle to enable/disable welcome messages.
|
||||
- [x] Add dropdown to select a channel for welcome messages.
|
||||
- [x] Add 3 default welcome message options.
|
||||
- [x] Add a custom welcome message option with a text input and apply button.
|
||||
- [x] **Leave Messages:**
|
||||
- [x] Add toggle to enable/disable leave messages.
|
||||
- [x] Add dropdown to select a channel for leave messages.
|
||||
- [x] Add 3 default leave message options.
|
||||
- [x] Add a custom leave message option with a text input and apply button.
|
||||
- [x] **Bot Integration:**
|
||||
- [x] Connect frontend settings to the backend.
|
||||
- [x] Implement bot logic to send welcome/leave messages based on server settings.
|
||||
|
||||
@@ -1,23 +1,16 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { readDb } = require('../../backend/db.js');
|
||||
|
||||
module.exports = {
|
||||
name: 'ping',
|
||||
description: 'Replies with Pong!',
|
||||
execute(interaction) {
|
||||
fs.readFile(path.join(__dirname, '../../backend/db.json'), 'utf8', (err, data) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return interaction.reply('An error occurred.');
|
||||
}
|
||||
const db = JSON.parse(data);
|
||||
const settings = db[interaction.guildId] || { pingCommand: false };
|
||||
const db = readDb();
|
||||
const settings = db[interaction.guildId] || { pingCommand: false };
|
||||
|
||||
if (settings.pingCommand) {
|
||||
interaction.reply('Pong!');
|
||||
} else {
|
||||
interaction.reply('The ping command is disabled on this server.');
|
||||
}
|
||||
});
|
||||
if (settings.pingCommand) {
|
||||
interaction.reply('Pong!');
|
||||
} else {
|
||||
interaction.reply('The ping command is disabled on this server.');
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,25 +1,26 @@
|
||||
require('dotenv').config({ path: '../backend/.env' });
|
||||
const { REST } = require('@discordjs/rest');
|
||||
const { Routes } = require('discord-api-types/v9');
|
||||
const { REST, Routes } = require('discord.js');
|
||||
|
||||
const commands = [{
|
||||
name: 'ping',
|
||||
description: 'Replies with Pong!',
|
||||
}];
|
||||
|
||||
const rest = new REST({ version: '9' }).setToken(process.env.DISCORD_BOT_TOKEN);
|
||||
const rest = new REST({ version: '10' }).setToken(process.env.DISCORD_BOT_TOKEN);
|
||||
|
||||
(async () => {
|
||||
const deployCommands = async (guildId) => {
|
||||
try {
|
||||
console.log('Started refreshing application (/) commands.');
|
||||
console.log(`Started refreshing application (/) commands for guild ${guildId}.`);
|
||||
|
||||
await rest.put(
|
||||
Routes.applicationGuildCommands(process.env.DISCORD_CLIENT_ID, process.env.GUILD_ID),
|
||||
Routes.applicationGuildCommands(process.env.DISCORD_CLIENT_ID, guildId),
|
||||
{ body: commands },
|
||||
);
|
||||
|
||||
console.log('Successfully reloaded application (/) commands.');
|
||||
console.log(`Successfully reloaded application (/) commands for guild ${guildId}.`);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
})();
|
||||
};
|
||||
|
||||
module.exports = deployCommands;
|
||||
|
||||
26
discord-bot/events/guildMemberAdd.js
Normal file
26
discord-bot/events/guildMemberAdd.js
Normal file
@@ -0,0 +1,26 @@
|
||||
const { Events } = require('discord.js');
|
||||
const { readDb } = require('../../backend/db.js');
|
||||
|
||||
module.exports = {
|
||||
name: Events.GuildMemberAdd,
|
||||
async execute(member) {
|
||||
try {
|
||||
const db = readDb();
|
||||
const settings = db[`${member.guild.id}_welcome_leave`];
|
||||
|
||||
if (settings && settings.welcome && settings.welcome.enabled && settings.welcome.channel) {
|
||||
const channel = member.guild.channels.cache.get(settings.welcome.channel);
|
||||
if (channel) {
|
||||
try {
|
||||
const message = settings.welcome.message.replace('{user}', member.user.toString());
|
||||
await channel.send(message);
|
||||
} catch (error) {
|
||||
console.error(`Could not send welcome message to channel ${settings.welcome.channel} in guild ${member.guild.id}:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error in guildMemberAdd event for guild ${member.guild.id}:`, error);
|
||||
}
|
||||
},
|
||||
};
|
||||
26
discord-bot/events/guildMemberRemove.js
Normal file
26
discord-bot/events/guildMemberRemove.js
Normal file
@@ -0,0 +1,26 @@
|
||||
const { Events } = require('discord.js');
|
||||
const { readDb } = require('../../backend/db.js');
|
||||
|
||||
module.exports = {
|
||||
name: Events.GuildMemberRemove,
|
||||
async execute(member) {
|
||||
try {
|
||||
const db = readDb();
|
||||
const settings = db[`${member.guild.id}_welcome_leave`];
|
||||
|
||||
if (settings && settings.leave && settings.leave.enabled && settings.leave.channel) {
|
||||
const channel = member.guild.channels.cache.get(settings.leave.channel);
|
||||
if (channel) {
|
||||
try {
|
||||
const message = settings.leave.message.replace('{user}', member.user.tag);
|
||||
await channel.send(message);
|
||||
} catch (error) {
|
||||
console.error(`Could not send leave message to channel ${settings.leave.channel} in guild ${member.guild.id}:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error in guildMemberRemove event for guild ${member.guild.id}:`, error);
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
const { ActivityType } = require('discord.js');
|
||||
|
||||
module.exports = {
|
||||
name: 'ready',
|
||||
name: 'clientReady',
|
||||
once: true,
|
||||
execute(client) {
|
||||
console.log('ECS - Full Stack Bot Online!');
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
const { Client, GatewayIntentBits, Collection } = require('discord.js');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const deployCommands = require('./deploy-commands');
|
||||
|
||||
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
|
||||
const client = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers] });
|
||||
|
||||
client.commands = new Collection();
|
||||
|
||||
@@ -27,6 +28,10 @@ client.on('interactionCreate', async interaction => {
|
||||
}
|
||||
});
|
||||
|
||||
client.on('guildCreate', guild => {
|
||||
deployCommands(guild.id);
|
||||
});
|
||||
|
||||
const login = () => {
|
||||
client.login(process.env.DISCORD_BOT_TOKEN);
|
||||
}
|
||||
|
||||
13
discord-bot/package-lock.json
generated
13
discord-bot/package-lock.json
generated
@@ -9,9 +9,8 @@
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@discordjs/rest": "^2.3.0",
|
||||
"discord-api-types": "^0.37.92",
|
||||
"discord.js": "^14.15.3",
|
||||
"crypto-js": "^4.2.0",
|
||||
"discord.js": "^14.22.1",
|
||||
"dotenv": "^16.4.5"
|
||||
}
|
||||
},
|
||||
@@ -218,10 +217,10 @@
|
||||
"npm": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/discord-api-types": {
|
||||
"version": "0.37.120",
|
||||
"resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.120.tgz",
|
||||
"integrity": "sha512-7xpNK0EiWjjDFp2nAhHXezE4OUWm7s1zhc/UXXN6hnFFU8dfoPHgV0Hx0RPiCa3ILRpdeh152icc68DGCyXYIw==",
|
||||
"node_modules/crypto-js": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz",
|
||||
"integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/discord.js": {
|
||||
|
||||
@@ -10,9 +10,8 @@
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"discord.js": "^14.15.3",
|
||||
"@discordjs/rest": "^2.3.0",
|
||||
"discord-api-types": "^0.37.92",
|
||||
"crypto-js": "^4.2.0",
|
||||
"discord.js": "^14.22.1",
|
||||
"dotenv": "^16.4.5"
|
||||
}
|
||||
}
|
||||
|
||||
68
errors.md
Normal file
68
errors.md
Normal file
@@ -0,0 +1,68 @@
|
||||
# Error Log
|
||||
|
||||
## ENOENT: no such file or directory, open 'F:\Projects\Github\ECS-Discordweb\discord-bot\events\...ackenddb.json'
|
||||
|
||||
**Context:** This error occurs in the `guildMemberAdd` and `guildMemberRemove` event handlers in the Discord bot when trying to read the `db.json` file.
|
||||
|
||||
**Initial Fix Attempts:**
|
||||
|
||||
1. **`path.join` with relative path:** Initially, the path was constructed using `path.join(__dirname, '..\..\backend\db.json')`. This was incorrect as it didn't go up enough directories.
|
||||
2. **`path.join` with corrected relative path:** The path was corrected to `path.join(__dirname, '..\..\..\backend\db.json')`. This seemed logically correct, but still resulted in the same error, with a strange `......` in the error path.
|
||||
3. **`path.resolve`:** The path construction was changed to use `path.resolve(__dirname, '..\..\..\backend\db.json')` to ensure an absolute path was resolved. This also failed with the same error.
|
||||
|
||||
**Root Cause Analysis:**
|
||||
|
||||
The exact cause of the path resolution failure is unclear, but it seems to be related to how `__dirname` and relative paths are handled when the bot module is required by the backend server. The inconsistent working directory might be the source of the problem.
|
||||
|
||||
**Solution:**
|
||||
|
||||
To provide a more robust and centralized solution, the database path will be managed within the `backend/db.js` file.
|
||||
|
||||
1. The `dbPath` will be exported from `backend/db.js`.
|
||||
2. The `guildMemberAdd.js` and `guildMemberRemove.js` event handlers will import the `dbPath` directly from `backend/db.js`, eliminating the need for path resolution in the event handlers themselves.
|
||||
|
||||
## SyntaxError: Unexpected token 'U', "U2FsdGVkX1"... is not valid JSON
|
||||
|
||||
**Context:** This error occurs in the `guildMemberAdd` and `guildMemberRemove` event handlers when trying to parse the content of `db.json`.
|
||||
|
||||
**Root Cause Analysis:**
|
||||
|
||||
The `db.json` file is encrypted, and the event handlers were reading the file content directly and trying to parse it as JSON. The encrypted string starts with "U2FsdGVkX1", which is not a valid JSON token, causing the `JSON.parse` to fail.
|
||||
|
||||
**Solution:**
|
||||
|
||||
The `backend/db.js` module already provides a `readDb` function that handles reading the file and decrypting its content. The event handlers must be updated to use this function instead of reading the file directly. This ensures that the encrypted data is correctly decrypted before being parsed as JSON.
|
||||
|
||||
## Outdated Discord.js Practices
|
||||
|
||||
**Context:** The `discord-bot` is using a slightly outdated version of `discord.js` (`^14.15.3` instead of the latest `^14.22.1`). Additionally, the `package.json` includes `@discordjs/rest` and `discord-api-types` as explicit dependencies, which are now bundled with `discord.js` v14 and can cause conflicts.
|
||||
|
||||
**Solution:**
|
||||
|
||||
1. Update the `discord.js` dependency to the latest stable version (`^14.22.1`).
|
||||
2. Remove the `@discordjs/rest` and `discord-api-types` dependencies from `package.json`.
|
||||
3. Run `npm install` to apply the changes and ensure all packages are up-to-date.
|
||||
|
||||
## Error: Cannot find module 'discord-api-types/v9'
|
||||
|
||||
**Context:** After removing the `discord-api-types` package, the application fails to start due to a missing module error in `discord-bot/deploy-commands.js`.
|
||||
|
||||
**Root Cause Analysis:**
|
||||
|
||||
The `deploy-commands.js` file was still referencing `discord-api-types/v9` for API-related enums or types. Since this package is no longer an explicit dependency (as it is bundled with `discord.js` v14), the import fails.
|
||||
|
||||
**Solution:**
|
||||
|
||||
The `deploy-commands.js` file needs to be updated to import the necessary modules, such as `Routes`, directly from `discord.js` instead of the now-removed `discord-api-types` package.
|
||||
|
||||
## DiscordAPIError[10004]: Unknown Guild
|
||||
|
||||
**Context:** The backend throws an "Unknown Guild" error when the frontend tries to fetch the channels for a server that the bot is not a member of.
|
||||
|
||||
**Root Cause Analysis:**
|
||||
|
||||
The `/api/servers/:guildId/channels` endpoint was attempting to fetch guild information regardless of whether the bot was actually in that guild. This caused a Discord API error when the guild was not found.
|
||||
|
||||
**Solution:**
|
||||
|
||||
The endpoint will be updated to first check if the guild exists in the bot's cache using `bot.client.guilds.cache.get(guildId)`. If the guild is not found, it will return an empty array, preventing the API error.
|
||||
28
frontend/src/components/ConfirmDialog.js
Normal file
28
frontend/src/components/ConfirmDialog.js
Normal file
@@ -0,0 +1,28 @@
|
||||
import React from 'react';
|
||||
import { Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, Button } from '@mui/material';
|
||||
|
||||
const ConfirmDialog = ({ open, onClose, onConfirm, title, message }) => {
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
>
|
||||
<DialogTitle>{title}</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>
|
||||
{message}
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onClose} color="primary">
|
||||
No
|
||||
</Button>
|
||||
<Button onClick={onConfirm} color="primary" autoFocus>
|
||||
Yes
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConfirmDialog;
|
||||
@@ -4,14 +4,19 @@ import { Grid, Card, CardContent, Typography, Box, CardMedia, IconButton, Snackb
|
||||
import { UserContext } from '../contexts/UserContext';
|
||||
import UserSettings from './UserSettings';
|
||||
import PersonAddIcon from '@mui/icons-material/PersonAdd';
|
||||
import RemoveCircleOutlineIcon from '@mui/icons-material/RemoveCircleOutline';
|
||||
import axios from 'axios';
|
||||
|
||||
import ConfirmDialog from './ConfirmDialog';
|
||||
|
||||
const Dashboard = () => {
|
||||
const { user, setUser } = useContext(UserContext);
|
||||
const [guilds, setGuilds] = useState([]);
|
||||
const [botStatus, setBotStatus] = useState({});
|
||||
const [snackbarOpen, setSnackbarOpen] = useState(false);
|
||||
const [snackbarMessage, setSnackbarMessage] = useState('');
|
||||
const [dialogOpen, setDialogOpen] = useState(false);
|
||||
const [selectedGuild, setSelectedGuild] = useState(null);
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
@@ -77,18 +82,34 @@ const Dashboard = () => {
|
||||
|
||||
const handleInviteBot = async (e, guild) => {
|
||||
e.stopPropagation();
|
||||
if (botStatus[guild.id]) {
|
||||
setSnackbarMessage('Bot already added to this server.');
|
||||
setSnackbarOpen(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const clientId = '1423377662055026840'; // Hardcoded client ID from user request
|
||||
const permissions = 8; // Administrator
|
||||
const inviteUrl = `https://discord.com/api/oauth2/authorize?client_id=${clientId}&permissions=${permissions}&scope=bot%20applications.commands&guild_id=${guild.id}&disable_guild_select=true`;
|
||||
window.open(inviteUrl, '_blank', 'noopener,noreferrer');
|
||||
};
|
||||
|
||||
const handleLeaveBot = (e, guild) => {
|
||||
e.stopPropagation();
|
||||
setSelectedGuild(guild);
|
||||
setDialogOpen(true);
|
||||
};
|
||||
|
||||
const handleConfirmLeave = async () => {
|
||||
if (!selectedGuild) return;
|
||||
try {
|
||||
await axios.post(`http://localhost:3002/api/servers/${selectedGuild.id}/leave`);
|
||||
setBotStatus(prevStatus => ({ ...prevStatus, [selectedGuild.id]: false }));
|
||||
setSnackbarMessage('Bot has left the server.');
|
||||
setSnackbarOpen(true);
|
||||
} catch (error) {
|
||||
console.error('Error leaving server:', error);
|
||||
setSnackbarMessage('Failed to make the bot leave the server.');
|
||||
setSnackbarOpen(true);
|
||||
}
|
||||
setDialogOpen(false);
|
||||
setSelectedGuild(null);
|
||||
};
|
||||
|
||||
const handleSnackbarClose = (event, reason) => {
|
||||
if (reason === 'clickaway') {
|
||||
return;
|
||||
@@ -158,14 +179,23 @@ const Dashboard = () => {
|
||||
{guild.name}
|
||||
</Box>
|
||||
</Box>
|
||||
<IconButton
|
||||
aria-label={`Invite bot to ${guild.name}`}
|
||||
size="small"
|
||||
onClick={(e) => handleInviteBot(e, guild)}
|
||||
disabled={botStatus[guild.id]}
|
||||
>
|
||||
<PersonAddIcon />
|
||||
</IconButton>
|
||||
{botStatus[guild.id] ? (
|
||||
<IconButton
|
||||
aria-label={`Make bot leave ${guild.name}`}
|
||||
size="small"
|
||||
onClick={(e) => handleLeaveBot(e, guild)}
|
||||
>
|
||||
<RemoveCircleOutlineIcon />
|
||||
</IconButton>
|
||||
) : (
|
||||
<IconButton
|
||||
aria-label={`Invite bot to ${guild.name}`}
|
||||
size="small"
|
||||
onClick={(e) => handleInviteBot(e, guild)}
|
||||
>
|
||||
<PersonAddIcon />
|
||||
</IconButton>
|
||||
)}
|
||||
</Box>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -177,6 +207,13 @@ const Dashboard = () => {
|
||||
{snackbarMessage}
|
||||
</Alert>
|
||||
</Snackbar>
|
||||
<ConfirmDialog
|
||||
open={dialogOpen}
|
||||
onClose={() => setDialogOpen(false)}
|
||||
onConfirm={handleConfirmLeave}
|
||||
title="Confirm Leave"
|
||||
message={`Are you sure you want the bot to leave ${selectedGuild?.name}?`}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Button, Container, Paper, Typography, Box } from '@mui/material';
|
||||
|
||||
const Login = () => {
|
||||
const navigate = useNavigate();
|
||||
@@ -16,10 +17,27 @@ const Login = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>Login</h2>
|
||||
<button onClick={handleLogin}>Login with Discord</button>
|
||||
</div>
|
||||
<Container component="main" maxWidth="xs" sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', minHeight: '100vh' }}>
|
||||
<Paper elevation={3} sx={{ padding: 4, display: 'flex', flexDirection: 'column', alignItems: 'center', borderRadius: '20px', boxShadow: '0 8px 16px 0 rgba(0,0,0,0.2)' }}>
|
||||
<Typography component="h1" variant="h4" sx={{ marginBottom: 2, fontWeight: 'bold' }}>
|
||||
Welcome!
|
||||
</Typography>
|
||||
<Typography component="p" variant="h6" sx={{ marginBottom: 4, textAlign: 'center' }}>
|
||||
Login with your Discord account to continue
|
||||
</Typography>
|
||||
<Box sx={{ width: '100%' }}>
|
||||
<Button
|
||||
fullWidth
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={handleLogin}
|
||||
sx={{ borderRadius: '10px', padding: '10px 0', textTransform: 'none', fontSize: '1.2rem' }}
|
||||
>
|
||||
Login with Discord
|
||||
</Button>
|
||||
</Box>
|
||||
</Paper>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useParams, useNavigate, useLocation } from 'react-router-dom';
|
||||
import axios from 'axios';
|
||||
import { Button, Typography, Card, CardContent, Box, IconButton } from '@mui/material';
|
||||
import { Button, Typography, Box, IconButton, Switch, Select, MenuItem, FormControl, FormControlLabel, Radio, RadioGroup, TextField, Accordion, AccordionSummary, AccordionDetails } from '@mui/material';
|
||||
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||
import UserSettings from './UserSettings';
|
||||
import ConfirmDialog from './ConfirmDialog';
|
||||
|
||||
const ServerSettings = () => {
|
||||
const { guildId } = useParams();
|
||||
@@ -13,6 +15,25 @@ const ServerSettings = () => {
|
||||
const [isBotInServer, setIsBotInServer] = useState(false);
|
||||
const [clientId, setClientId] = useState(null);
|
||||
const [server, setServer] = useState(null);
|
||||
const [dialogOpen, setDialogOpen] = useState(false);
|
||||
const [channels, setChannels] = useState([]);
|
||||
const [welcomeLeaveSettings, setWelcomeLeaveSettings] = useState({
|
||||
welcome: {
|
||||
enabled: false,
|
||||
channel: '',
|
||||
message: 'Welcome to the server, {user}!',
|
||||
customMessage: '',
|
||||
},
|
||||
leave: {
|
||||
enabled: false,
|
||||
channel: '',
|
||||
message: '{user} has left the server.',
|
||||
customMessage: '',
|
||||
},
|
||||
});
|
||||
|
||||
const defaultWelcomeMessages = ["Welcome to the server, {user}!", "Hey {user}, welcome!", "{user} has joined the party!"];
|
||||
const defaultLeaveMessages = ["{user} has left the server.", "Goodbye, {user}.", "We'll miss you, {user}."];
|
||||
|
||||
useEffect(() => {
|
||||
if (location.state && location.state.guild) {
|
||||
@@ -44,8 +65,77 @@ const ServerSettings = () => {
|
||||
setClientId(response.data.clientId);
|
||||
});
|
||||
|
||||
// Fetch channels
|
||||
axios.get(`http://localhost:3002/api/servers/${guildId}/channels`)
|
||||
.then(response => {
|
||||
setChannels(response.data);
|
||||
});
|
||||
|
||||
// Fetch welcome/leave settings
|
||||
axios.get(`http://localhost:3002/api/servers/${guildId}/welcome-leave-settings`)
|
||||
.then(response => {
|
||||
if (response.data) {
|
||||
setWelcomeLeaveSettings(response.data);
|
||||
}
|
||||
});
|
||||
|
||||
}, [guildId, location.state]);
|
||||
|
||||
const handleSettingUpdate = (newSettings) => {
|
||||
axios.post(`http://localhost:3002/api/servers/${guildId}/welcome-leave-settings`, newSettings)
|
||||
.then(response => {
|
||||
if (response.data.success) {
|
||||
setWelcomeLeaveSettings(newSettings);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const handleToggleChange = (type) => (event) => {
|
||||
const newSettings = { ...welcomeLeaveSettings };
|
||||
newSettings[type].enabled = event.target.checked;
|
||||
handleSettingUpdate(newSettings);
|
||||
};
|
||||
|
||||
const handleChannelChange = (type) => (event) => {
|
||||
const newSettings = { ...welcomeLeaveSettings };
|
||||
newSettings[type].channel = event.target.value;
|
||||
handleSettingUpdate(newSettings);
|
||||
};
|
||||
|
||||
const handleMessageOptionChange = (type) => (event) => {
|
||||
const newSettings = { ...welcomeLeaveSettings };
|
||||
if (event.target.value !== 'custom') {
|
||||
newSettings[type].message = event.target.value;
|
||||
handleSettingUpdate(newSettings);
|
||||
} else {
|
||||
const tempSettings = { ...welcomeLeaveSettings };
|
||||
// Set message to custom message to get the radio button to select custom
|
||||
tempSettings[type].message = tempSettings[type].customMessage;
|
||||
setWelcomeLeaveSettings(tempSettings);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCustomMessageChange = (type) => (event) => {
|
||||
const newSettings = { ...welcomeLeaveSettings };
|
||||
newSettings[type].customMessage = event.target.value;
|
||||
setWelcomeLeaveSettings(newSettings);
|
||||
};
|
||||
|
||||
const handleApplyCustomMessage = (type) => () => {
|
||||
const newSettings = { ...welcomeLeaveSettings };
|
||||
newSettings[type].message = newSettings[type].customMessage;
|
||||
handleSettingUpdate(newSettings);
|
||||
};
|
||||
|
||||
const getMessageValue = (type) => {
|
||||
const currentMessage = welcomeLeaveSettings[type].message;
|
||||
const defaultMessages = type === 'welcome' ? defaultWelcomeMessages : defaultLeaveMessages;
|
||||
if (defaultMessages.includes(currentMessage)) {
|
||||
return currentMessage;
|
||||
}
|
||||
return 'custom';
|
||||
}
|
||||
|
||||
const togglePingCommand = () => {
|
||||
const newSettings = { ...settings, pingCommand: !settings.pingCommand };
|
||||
axios.post(`http://localhost:3002/api/servers/${guildId}/settings`, newSettings)
|
||||
@@ -63,6 +153,20 @@ const ServerSettings = () => {
|
||||
window.open(url, '_blank');
|
||||
};
|
||||
|
||||
const handleLeaveBot = () => {
|
||||
setDialogOpen(true);
|
||||
};
|
||||
|
||||
const handleConfirmLeave = async () => {
|
||||
try {
|
||||
await axios.post(`http://localhost:3002/api/servers/${guildId}/leave`);
|
||||
setIsBotInServer(false);
|
||||
} catch (error) {
|
||||
console.error('Error leaving server:', error);
|
||||
}
|
||||
setDialogOpen(false);
|
||||
};
|
||||
|
||||
const handleBack = () => {
|
||||
navigate('/dashboard');
|
||||
}
|
||||
@@ -78,7 +182,9 @@ const ServerSettings = () => {
|
||||
{server ? `Server Settings for ${server.name}` : 'Loading...'}
|
||||
</Typography>
|
||||
{isBotInServer ? (
|
||||
<Typography>The bot is already in this server.</Typography>
|
||||
<Button variant="contained" size="small" color="error" onClick={handleLeaveBot}>
|
||||
Leave Server
|
||||
</Button>
|
||||
) : (
|
||||
<Button variant="contained" size="small" onClick={handleInviteBot} disabled={!clientId}>
|
||||
Invite Bot
|
||||
@@ -87,23 +193,125 @@ const ServerSettings = () => {
|
||||
</Box>
|
||||
<UserSettings />
|
||||
</Box>
|
||||
<Card sx={{ borderRadius: '20px', boxShadow: '0 8px 16px 0 rgba(0,0,0,0.2)', marginTop: '20px' }}>
|
||||
<CardContent>
|
||||
<Accordion sx={{ marginTop: '20px', opacity: isBotInServer ? 1 : 0.5 }}>
|
||||
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
||||
<Typography variant="h6">Commands</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
{!isBotInServer && <Typography>Invite the bot to enable commands.</Typography>}
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginTop: '10px' }}>
|
||||
<Typography>Ping Command</Typography>
|
||||
<Button variant="contained" onClick={togglePingCommand}>
|
||||
<Button variant="contained" onClick={togglePingCommand} disabled={!isBotInServer}>
|
||||
{settings.pingCommand ? 'Disable' : 'Enable'}
|
||||
</Button>
|
||||
</Box>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card sx={{ borderRadius: '20px', boxShadow: '0 8px 16px 0 rgba(0,0,0,0.2)', marginTop: '20px' }}>
|
||||
<CardContent>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
<Accordion sx={{ marginTop: '20px', opacity: isBotInServer ? 1 : 0.5 }}>
|
||||
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
||||
<Typography variant="h6">Welcome/Leave</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
{!isBotInServer && <Typography>Invite the bot to enable this feature.</Typography>}
|
||||
<Box sx={{ marginTop: '10px' }}>
|
||||
<Typography variant="subtitle1">Welcome Messages</Typography>
|
||||
<FormControlLabel
|
||||
control={<Switch checked={welcomeLeaveSettings.welcome.enabled} onChange={handleToggleChange('welcome')} disabled={!isBotInServer} />}
|
||||
label="Enable Welcome Messages"
|
||||
/>
|
||||
<FormControl fullWidth sx={{ marginTop: '10px' }} disabled={!isBotInServer || !welcomeLeaveSettings.welcome.enabled}>
|
||||
<Select
|
||||
value={welcomeLeaveSettings.welcome.channel}
|
||||
onChange={handleChannelChange('welcome')}
|
||||
displayEmpty
|
||||
>
|
||||
<MenuItem value="" disabled>Select a channel</MenuItem>
|
||||
{channels.map(channel => (
|
||||
<MenuItem key={channel.id} value={channel.id}>{channel.name}</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl component="fieldset" sx={{ marginTop: '10px' }} disabled={!isBotInServer || !welcomeLeaveSettings.welcome.enabled}>
|
||||
<RadioGroup
|
||||
value={getMessageValue('welcome')}
|
||||
onChange={handleMessageOptionChange('welcome')}
|
||||
>
|
||||
{defaultWelcomeMessages.map(msg => (
|
||||
<FormControlLabel key={msg} value={msg} control={<Radio />} label={msg} />
|
||||
))}
|
||||
<FormControlLabel value="custom" control={<Radio />} label="Custom" />
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', marginTop: '10px' }} >
|
||||
<TextField
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
placeholder="Your custom message"
|
||||
value={welcomeLeaveSettings.welcome.customMessage}
|
||||
onChange={handleCustomMessageChange('welcome')}
|
||||
disabled={!isBotInServer || !welcomeLeaveSettings.welcome.enabled || getMessageValue('welcome') !== 'custom'}
|
||||
/>
|
||||
<Button onClick={handleApplyCustomMessage('welcome')} disabled={!isBotInServer || !welcomeLeaveSettings.welcome.enabled || getMessageValue('welcome') !== 'custom'}>Apply</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box sx={{ marginTop: '20px' }}>
|
||||
<Typography variant="subtitle1">Leave Messages</Typography>
|
||||
<FormControlLabel
|
||||
control={<Switch checked={welcomeLeaveSettings.leave.enabled} onChange={handleToggleChange('leave')} disabled={!isBotInServer} />}
|
||||
label="Enable Leave Messages"
|
||||
/>
|
||||
<FormControl fullWidth sx={{ marginTop: '10px' }} disabled={!isBotInServer || !welcomeLeaveSettings.leave.enabled}>
|
||||
<Select
|
||||
value={welcomeLeaveSettings.leave.channel}
|
||||
onChange={handleChannelChange('leave')}
|
||||
displayEmpty
|
||||
>
|
||||
<MenuItem value="" disabled>Select a channel</MenuItem>
|
||||
{channels.map(channel => (
|
||||
<MenuItem key={channel.id} value={channel.id}>{channel.name}</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl component="fieldset" sx={{ marginTop: '10px' }} disabled={!isBotInServer || !welcomeLeaveSettings.leave.enabled}>
|
||||
<RadioGroup
|
||||
value={getMessageValue('leave')}
|
||||
onChange={handleMessageOptionChange('leave')}
|
||||
>
|
||||
{defaultLeaveMessages.map(msg => (
|
||||
<FormControlLabel key={msg} value={msg} control={<Radio />} label={msg} />
|
||||
))}
|
||||
<FormControlLabel value="custom" control={<Radio />} label="Custom" />
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', marginTop: '10px' }} >
|
||||
<TextField
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
placeholder="Your custom message"
|
||||
value={welcomeLeaveSettings.leave.customMessage}
|
||||
onChange={handleCustomMessageChange('leave')}
|
||||
disabled={!isBotInServer || !welcomeLeaveSettings.leave.enabled || getMessageValue('leave') !== 'custom'}
|
||||
/>
|
||||
<Button onClick={handleApplyCustomMessage('leave')} disabled={!isBotInServer || !welcomeLeaveSettings.leave.enabled || getMessageValue('leave') !== 'custom'}>Apply</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
<Accordion sx={{ marginTop: '20px', opacity: isBotInServer ? 1 : 0.5 }}>
|
||||
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
||||
<Typography variant="h6">Admin Commands</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<Typography>Coming soon...</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
<ConfirmDialog
|
||||
open={dialogOpen}
|
||||
onClose={() => setDialogOpen(false)}
|
||||
onConfirm={handleConfirmLeave}
|
||||
title="Confirm Leave"
|
||||
message={`Are you sure you want the bot to leave ${server?.name}?`}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user