diff --git a/app/bot.js b/app/bot.js index 5596ce7..7791ce6 100644 --- a/app/bot.js +++ b/app/bot.js @@ -42,6 +42,18 @@ client.on(Events.GuildMemberAdd, member => { } } ); + db.get( + "SELECT enabled, role_id FROM autorole_newuser_config WHERE guild_id = ?", + [member.guild.id], + (err, row) => { + if (err || !row || !row.enabled) return; + + const role = member.guild.roles.cache.get(row.role_id); + if (role) { + member.roles.add(role); + } + } + ); }); @@ -67,6 +79,42 @@ client.on(Events.GuildMemberRemove, member => { }); +client.on(Events.VoiceStateUpdate, (oldState, newState) => { + if (newState.member.user.bot) return; + + const guildId = newState.guild.id; + + db.get( + "SELECT enabled, role_id, exclude_channel_ids FROM autorole_vocal_config WHERE guild_id = ?", + [guildId], + (err, row) => { + if (err || !row || !row.enabled) return; + + let excludeChannelIds = []; + try { + excludeChannelIds = row.exclude_channel_ids + ? JSON.parse(row.exclude_channel_ids) + : []; + } catch (err) { + console.error("Erreur parsing exclude_channel_ids", err); + excludeChannelIds = []; + } + + const role = newState.guild.roles.cache.get(row.role_id); + if (!role) return; + + // User joins a voice channel and it's not excluded et a pas déjà le rôle + if (newState.channelId && !excludeChannelIds.includes(newState.channelId) && !newState.member.roles.cache.has(role.id)) { + newState.member.roles.add(role); + } + // User leaves a voice channel or joins an excluded one + else if (!newState.channelId || excludeChannelIds.includes(newState.channelId)) { + newState.member.roles.remove(role); + } + } + ); +}); + client.login(process.env.BOT_TOKEN); module.exports = client; diff --git a/app/db.js b/app/db.js index 9ba6d5e..f3323e4 100644 --- a/app/db.js +++ b/app/db.js @@ -24,6 +24,19 @@ db.exec(` enabled INTEGER NOT NULL, message TEXT NOT NULL ); + + CREATE TABLE IF NOT EXISTS autorole_newuser_config ( + guild_id TEXT PRIMARY KEY, + role_id TEXT, + enabled INTEGER NOT NULL + ); + + CREATE TABLE IF NOT EXISTS autorole_vocal_config ( + guild_id TEXT PRIMARY KEY, + role_id TEXT, + exclude_channel_ids TEXT, + enabled INTEGER NOT NULL + ); `); module.exports = db; diff --git a/app/public/guild.css b/app/public/guild.css index 5598f6f..f5f356b 100644 --- a/app/public/guild.css +++ b/app/public/guild.css @@ -139,4 +139,61 @@ small code { form { padding: 15px; } -} \ No newline at end of file +} + +#autorole-vocal-exclude-channel { + background-color: #0f1115; + color: #e5e7eb; + + border: 1px solid #2a2f3a; + border-radius: 8px; + + padding: 8px; + font-size: 14px; + + min-width: 240px; + + outline: none; +} + +/* options */ +#autorole-vocal-exclude-channel option { + background-color: #0f1115; + color: #e5e7eb; + padding: 6px; +} + +/* hover */ +#autorole-vocal-exclude-channel option:hover { + background-color: #1f2937; +} + +/* sélection */ +#autorole-vocal-exclude-channel option:checked { + background-color: #2563eb; /* bleu */ + color: #ffffff; +} + +/* focus */ +#autorole-vocal-exclude-channel:focus { + border-color: #2563eb; + box-shadow: 0 0 0 1px #2563eb; +} + +/* scrollbar (Chrome / Edge) */ +#autorole-vocal-exclude-channel::-webkit-scrollbar { + width: 8px; +} + +#autorole-vocal-exclude-channel::-webkit-scrollbar-track { + background: #0f1115; +} + +#autorole-vocal-exclude-channel::-webkit-scrollbar-thumb { + background: #2a2f3a; + border-radius: 4px; +} + +#autorole-vocal-exclude-channel::-webkit-scrollbar-thumb:hover { + background: #3b4252; +} diff --git a/app/public/guild.html b/app/public/guild.html index 20fe235..7e16abe 100644 --- a/app/public/guild.html +++ b/app/public/guild.html @@ -88,6 +88,49 @@
+ + +
+ + + + + +
+
+ + +
+ + + + + + + +
+
diff --git a/app/public/index.html b/app/public/index.html index 2fb155c..24bda2f 100644 --- a/app/public/index.html +++ b/app/public/index.html @@ -13,7 +13,7 @@
Avatar - Se déconnecter + Se déconnecter

LazyBot

diff --git a/app/routes/api.js b/app/routes/api.js new file mode 100644 index 0000000..d61221d --- /dev/null +++ b/app/routes/api.js @@ -0,0 +1,339 @@ +const express = require("express"); +const router = express.Router(); + +module.exports = (app, db, client) => { + + // --- User info --- + router.get("/user", (req, res) => { + if (req.session.user) res.json(req.session.user); + else res.status(401).json({ error: "Utilisateur non connecté" }); + }); + + router.get("/guilds", (req, res) => { + const userGuilds = req.session.guilds; + if (!userGuilds) return res.status(401).json({ error: "Utilisateur non connecté" }); + + const botGuildIds = client.guilds.cache.map(g => g.id); + const validGuilds = userGuilds.filter(g => { + const hasAdmin = (g.permissions & 0x8) === 0x8; + return hasAdmin && botGuildIds.includes(g.id); + }); + + res.json(validGuilds); + }); + + + // API pour sauvegarder la configuration de bienvenue + router.post("/bot/save-welcome-config", express.json(), (req, res) => { + const { guildId, channelId, welcomeEnabled, welcomeMessage } = req.body; + + if (!req.session.guilds) { + return res.status(401).json({ success: false }); + } + + const isAdmin = req.session.guilds.find( + g => g.id === guildId && (BigInt(g.permissions) & 0x8n) === 0x8n + ); + + if (!isAdmin) { + return res.status(403).json({ success: false }); + } + + db.run( + ` + INSERT INTO welcome_config (guild_id, channel_id, enabled, message) + VALUES (?, ?, ?, ?) + ON CONFLICT(guild_id) + DO UPDATE SET channel_id = ?, enabled = ?, message = ? + `, + [ + guildId, + channelId, + welcomeEnabled ? 1 : 0, + welcomeMessage, + channelId, + welcomeEnabled ? 1 : 0, + welcomeMessage + ], + err => { + if (err) { + console.error(err); + return res.status(500).json({ success: false }); + } + res.json({ success: true }); + } + ); + }); + + + router.get("/bot/get-welcome-config/:guildId", (req, res) => { + const { guildId } = req.params; + + db.get( + "SELECT enabled, channel_id, message FROM welcome_config WHERE guild_id = ?", + [guildId], + (err, row) => { + if (err || !row) { + return res.json({ enabled: false, channelId: null, message: "" }); + } + res.json({ + enabled: !!row.enabled, + channelId: row.channel_id, + message: row.message + }); + } + ); + }); + + + router.post("/bot/save-goodbye-config", express.json(), (req, res) => { + const { guildId, channelId, goodbyeEnabled, goodbyeMessage } = req.body; + + if (!req.session.guilds) { + return res.status(401).json({ success: false }); + } + + const isAdmin = req.session.guilds.find( + g => g.id === guildId && (BigInt(g.permissions) & 0x8n) === 0x8n + ); + + if (!isAdmin) { + return res.status(403).json({ success: false }); + } + + db.run( + ` + INSERT INTO goodbye_config (guild_id, channel_id, enabled, message) + VALUES (?, ?, ?, ?) + ON CONFLICT(guild_id) + DO UPDATE SET channel_id = ?, enabled = ?, message = ? + `, + [ + guildId, + channelId, + goodbyeEnabled ? 1 : 0, + goodbyeMessage, + channelId, + goodbyeEnabled ? 1 : 0, + goodbyeMessage + ], + err => { + if (err) { + console.error(err); + return res.status(500).json({ success: false }); + } + res.json({ success: true }); + } + ); + }); + + + router.get("/bot/get-goodbye-config/:guildId", (req, res) => { + const { guildId } = req.params; + + db.get( + "SELECT enabled, channel_id, message FROM goodbye_config WHERE guild_id = ?", + [guildId], + (err, row) => { + if (err || !row) { + return res.json({ enabled: false, channelId: null, message: "" }); + } + res.json({ + enabled: !!row.enabled, + channelId: row.channel_id, + message: row.message + }); + } + ); + }); + + + router.post("/bot/save-autorole-newuser-config", express.json(), (req, res) => { + const { guildId, roleId, enabled } = req.body; + console.log("Received autorole-newuser config:", { guildId, roleId, enabled }); + if (!req.session.guilds) { + return res.status(401).json({ success: false }); + } + + const isAdmin = req.session.guilds.find( + g => g.id === guildId && (BigInt(g.permissions) & 0x8n) === 0x8n + ); + + if (!isAdmin) { + return res.status(403).json({ success: false }); + } + + db.run( + ` + INSERT INTO autorole_newuser_config (guild_id, role_id, enabled) + VALUES (?, ?, ?) + ON CONFLICT(guild_id) + DO UPDATE SET role_id = ?, enabled = ? + `, + [ + guildId, + roleId, + enabled ? 1 : 0, + roleId, + enabled ? 1 : 0 + ], + err => { + if (err) { + console.error(err); + return res.status(500).json({ success: false }); + } + res.json({ success: true }); + } + ); + }); + + router.get("/bot/get-autorole-newuser-config/:guildId", (req, res) => { + const { guildId } = req.params; + + db.get( + "SELECT enabled, role_id FROM autorole_newuser_config WHERE guild_id = ?", + [guildId], + (err, row) => { + if (err || !row) { + return res.json({ enabled: false, roleId: null }); + } + res.json({ + enabled: !!row.enabled, + roleId: row.role_id + }); + } + ); + }); + + + router.post("/bot/save-autorole-vocal-config", express.json(), (req, res) => { + const { guildId, roleId, excludeChannelId, enabled } = req.body; + + if (!req.session.guilds) { + return res.status(401).json({ success: false }); + } + + const isAdmin = req.session.guilds.find( + g => g.id === guildId && (BigInt(g.permissions) & 0x8n) === 0x8n + ); + + if (!isAdmin) { + return res.status(403).json({ success: false }); + } + + const excludeChannelIdArray = Array.isArray(excludeChannelId) ? excludeChannelId : [excludeChannelId]; + + db.run( + ` + INSERT INTO autorole_vocal_config (guild_id, role_id, exclude_channel_ids, enabled) + VALUES (?, ?, ?, ?) + ON CONFLICT(guild_id) + DO UPDATE SET role_id = ?, exclude_channel_ids = ?, enabled = ? + `, + [ + guildId, + roleId, + JSON.stringify(excludeChannelIdArray), + enabled ? 1 : 0, + roleId, + JSON.stringify(excludeChannelIdArray), + enabled ? 1 : 0 + ], + err => { + if (err) { + console.error(err); + return res.status(500).json({ success: false }); + } + res.json({ success: true }); + } + ); + }); + + router.get("/bot/get-autorole-vocal-config/:guildId", (req, res) => { + const { guildId } = req.params; + + db.get( + "SELECT enabled, role_id, exclude_channel_ids FROM autorole_vocal_config WHERE guild_id = ?", + [guildId], + (err, row) => { + if (err || !row) { + return res.json({ enabled: false, roleId: null }); + } + res.json({ + enabled: !!row.enabled, + roleId: row.role_id, + excludeChannelIds: JSON.parse(row.exclude_channel_ids), + }); + } + ); + }); + + + router.get("/bot/get-text-channels/:guildId", (req, res) => { + const { guildId } = req.params; + const guild = client.guilds.cache.get(guildId); + if (!guild) { + return res.status(404).json({ error: "Serveur non trouvé" }); + } + + const channels = guild.channels.cache + .filter(channel => channel.isTextBased()) + .map(channel => ({ + id: channel.id, + name: channel.name + })); + + res.json(channels); + }); + + + router.get("/bot/get-voice-channels/:guildId", (req, res) => { + const { guildId } = req.params; + const guild = client.guilds.cache.get(guildId); + if (!guild) { + return res.status(404).json({ error: "Serveur non trouvé" }); + } + + const channels = guild.channels.cache + .filter(channel => channel.isVoiceBased()) + .map(channel => ({ + id: channel.id, + name: channel.name + })); + + res.json(channels); + }); + + + router.get("/bot/get-roles/:guildId", (req, res) => { + const { guildId } = req.params; + const guild = client.guilds.cache.get(guildId); + if (!guild) { + return res.status(404).json({ error: "Serveur non trouvé" }); + } + + const botMember = guild.members.cache.get(client.user.id); + if (!botMember) return res.status(500).json({ error: "Bot non trouvé dans ce serveur" }); + + const botRolePos = botMember.roles.highest.position; + + // On filtre : + // - rôle sous le plus haut rôle du bot + // - pas @everyone + // - pas managed (roles de bot/intégrations) + const roles = guild.roles.cache + .filter(role => + role.position < botRolePos && + role.name !== "@everyone" && + role.managed === false + ) + .map(role => ({ + id: role.id, + name: role.name + })); + + res.json(roles); + }); + + app.use("/api", router); +}; diff --git a/app/routes/auth.js b/app/routes/auth.js new file mode 100644 index 0000000..a97cab5 --- /dev/null +++ b/app/routes/auth.js @@ -0,0 +1,65 @@ +const express = require("express"); +const fetch = require("cross-fetch"); +const router = express.Router(); +const path = require("path"); + +const CLIENT_ID = process.env.CLIENT_ID; +const CLIENT_SECRET = process.env.CLIENT_SECRET; +const REDIRECT_URI = process.env.REDIRECT_URI; + +module.exports = (app, db, client) => { + // --- Connexion Discord --- + router.get("/discord", (req, res) => { + const url = `https://discord.com/api/oauth2/authorize?client_id=${CLIENT_ID}&redirect_uri=${encodeURIComponent(REDIRECT_URI)}&response_type=code&scope=identify%20guilds`; + res.redirect(url); + }); + + router.get("/discord/callback", async (req, res) => { + const code = req.query.code; + if (!code) return res.send("Pas de code OAuth reçu !"); + + try { + const data = new URLSearchParams(); + data.append("client_id", CLIENT_ID); + data.append("client_secret", CLIENT_SECRET); + data.append("grant_type", "authorization_code"); + data.append("code", code); + data.append("redirect_uri", REDIRECT_URI); + data.append("scope", "identify"); + + const tokenResponse = await fetch("https://discord.com/api/oauth2/token", { + method: "POST", + body: data, + headers: { "Content-Type": "application/x-www-form-urlencoded" }, + }); + + const tokenJson = await tokenResponse.json(); + const accessToken = tokenJson.access_token; + + const userResponse = await fetch("https://discord.com/api/users/@me", { + headers: { Authorization: `Bearer ${accessToken}` }, + }); + const user = await userResponse.json(); + + const guildsResponse = await fetch("https://discord.com/api/users/@me/guilds", { + headers: { Authorization: `Bearer ${accessToken}` }, + }); + const guilds = await guildsResponse.json(); + + req.session.user = user; + req.session.guilds = guilds; + + res.redirect("/dashboard"); + } catch (err) { + console.error(err); + res.send("Erreur lors de la connexion Discord !"); + } + }); + + router.get("/logout", (req, res) => { + req.session.destroy(); + res.redirect("/"); + }); + + app.use("/auth", router); +}; diff --git a/app/server.js b/app/server.js index 41cdcda..94c2dc9 100644 --- a/app/server.js +++ b/app/server.js @@ -31,94 +31,9 @@ app.use(session({ // --- Servir le dossier public --- app.use(express.static(path.join(__dirname, "public"))); -// --- Route pour démarrer la connexion Discord --- -app.get("/auth/discord", (req, res) => { - const url = `https://discord.com/api/oauth2/authorize?client_id=${CLIENT_ID}&redirect_uri=${encodeURIComponent(REDIRECT_URI)}&response_type=code&scope=identify%20guilds`; - res.redirect(url); -}); - -// --- Callback après connexion Discord --- -app.get("/auth/discord/callback", async (req, res) => { - const code = req.query.code; - if (!code) return res.send("Pas de code OAuth reçu !"); - - try { - // Échange du code contre access token - const data = new URLSearchParams(); - data.append("client_id", CLIENT_ID); - data.append("client_secret", CLIENT_SECRET); - data.append("grant_type", "authorization_code"); - data.append("code", code); - data.append("redirect_uri", REDIRECT_URI); - data.append("scope", "identify"); - - const tokenResponse = await fetch("https://discord.com/api/oauth2/token", { - method: "POST", - body: data, - headers: { "Content-Type": "application/x-www-form-urlencoded" }, - }); - - const tokenJson = await tokenResponse.json(); - const accessToken = tokenJson.access_token; - - // Récupération des infos utilisateur - const userResponse = await fetch("https://discord.com/api/users/@me", { - headers: { Authorization: `Bearer ${accessToken}` }, - }); - const user = await userResponse.json(); - - // Récupérer la liste des guilds - const guildsResponse = await fetch("https://discord.com/api/users/@me/guilds", { - headers: { Authorization: `Bearer ${accessToken}` }, - }); - const guilds = await guildsResponse.json(); - - // Stocker l'utilisateur dans la session - req.session.user = user; - req.session.guilds = guilds; - - // Rediriger vers la page HTML - res.redirect("/dashboard"); - } catch (err) { - console.error(err); - res.send("Erreur lors de la connexion Discord !"); - } -}); - - -app.get("/logout", (req, res) => { - req.session.destroy(); - res.redirect("/"); -}); - - -// --- API pour récupérer l'objet user côté front --- -app.get("/api/user", (req, res) => { - if (req.session.user) { - res.json(req.session.user); - } else { - res.status(401).json({ error: "Utilisateur non connecté" }); - } -}); - - -app.get("/api/guilds", (req, res) => { - const userGuilds = req.session.guilds; // toutes les guilds de l'utilisateur - if (!userGuilds) return res.status(401).json({ error: "Utilisateur non connecté" }); - - // Liste des guilds où le bot est présent - const botGuildIds = client.guilds.cache.map(g => g.id); - - // Filtrer : bot présent + admin - const validGuilds = userGuilds.filter(g => { - const hasAdmin = (g.permissions & 0x8) === 0x8; // flag admin - const botHere = botGuildIds.includes(g.id); - return hasAdmin && botHere; - }); - - res.json(validGuilds); -}); - +// --- Routes --- +require("./routes/auth")(app, db, client); +require("./routes/api")(app, db, client); app.get("/invite-bot", (req, res) => { const permissions = 8; // Permissions administrateur @@ -142,7 +57,6 @@ app.get("/invite-bot", (req, res) => { res.json({ url }); }); - // Servir le dashboard par serveur app.get("/guild/:guildId", (req, res) => { const guildId = req.params.guildId; @@ -159,150 +73,6 @@ app.get("/guild/:guildId", (req, res) => { res.sendFile(path.join(__dirname, "public", "guild.html")); }); - -// API pour sauvegarder la configuration de bienvenue -app.post("/api/bot/save-welcome-config", express.json(), (req, res) => { - const { guildId, channelId, welcomeEnabled, welcomeMessage } = req.body; - - if (!req.session.guilds) { - return res.status(401).json({ success: false }); - } - - const isAdmin = req.session.guilds.find( - g => g.id === guildId && (BigInt(g.permissions) & 0x8n) === 0x8n - ); - - if (!isAdmin) { - return res.status(403).json({ success: false }); - } - - db.run( - ` - INSERT INTO welcome_config (guild_id, channel_id, enabled, message) - VALUES (?, ?, ?, ?) - ON CONFLICT(guild_id) - DO UPDATE SET channel_id = ?, enabled = ?, message = ? - `, - [ - guildId, - channelId, - welcomeEnabled ? 1 : 0, - welcomeMessage, - channelId, - welcomeEnabled ? 1 : 0, - welcomeMessage - ], - err => { - if (err) { - console.error(err); - return res.status(500).json({ success: false }); - } - res.json({ success: true }); - } - ); -}); - - -app.get("/api/bot/get-welcome-config/:guildId", (req, res) => { - const { guildId } = req.params; - - db.get( - "SELECT enabled, channel_id, message FROM welcome_config WHERE guild_id = ?", - [guildId], - (err, row) => { - if (err || !row) { - return res.json({ enabled: false, channelId: null, message: "" }); - } - res.json({ - enabled: !!row.enabled, - channelId: row.channel_id, - message: row.message - }); - } - ); -}); - - -app.post("/api/bot/save-goodbye-config", express.json(), (req, res) => { - const { guildId, channelId, goodbyeEnabled, goodbyeMessage } = req.body; - - if (!req.session.guilds) { - return res.status(401).json({ success: false }); - } - - const isAdmin = req.session.guilds.find( - g => g.id === guildId && (BigInt(g.permissions) & 0x8n) === 0x8n - ); - - if (!isAdmin) { - return res.status(403).json({ success: false }); - } - - db.run( - ` - INSERT INTO goodbye_config (guild_id, channel_id, enabled, message) - VALUES (?, ?, ?, ?) - ON CONFLICT(guild_id) - DO UPDATE SET channel_id = ?, enabled = ?, message = ? - `, - [ - guildId, - channelId, - goodbyeEnabled ? 1 : 0, - goodbyeMessage, - channelId, - goodbyeEnabled ? 1 : 0, - goodbyeMessage - ], - err => { - if (err) { - console.error(err); - return res.status(500).json({ success: false }); - } - res.json({ success: true }); - } - ); -}); - - -app.get("/api/bot/get-goodbye-config/:guildId", (req, res) => { - const { guildId } = req.params; - - db.get( - "SELECT enabled, channel_id, message FROM goodbye_config WHERE guild_id = ?", - [guildId], - (err, row) => { - if (err || !row) { - return res.json({ enabled: false, channelId: null, message: "" }); - } - res.json({ - enabled: !!row.enabled, - channelId: row.channel_id, - message: row.message - }); - } - ); -}); - - -app.get("/api/bot/get-text-channels/:guildId", (req, res) => { - const { guildId } = req.params; - const guild = client.guilds.cache.get(guildId); - if (!guild) { - return res.status(404).json({ error: "Serveur non trouvé" }); - } - - const channels = guild.channels.cache - .filter(channel => channel.isTextBased()) - .map(channel => ({ - id: channel.id, - name: channel.name - })); - - res.json(channels); -}); - - app.get("/dashboard", (req, res) => { if (!req.session.user) { return res.redirect("/auth/discord"); @@ -313,4 +83,4 @@ app.get("/dashboard", (req, res) => { // --- Lancement du serveur --- -app.listen(PORT, () => console.log(`Serveur lancé sur http://localhost:${PORT}`)); +app.listen(PORT, () => console.log(`Serveur lancé sur http://localhost:${PORT}`)); \ No newline at end of file