add a major part of user level

This commit is contained in:
Arthur Puechberty
2026-01-16 23:41:39 +01:00
parent 20f7e080a8
commit 08b444efdd
10 changed files with 810 additions and 207 deletions
+138 -206
View File
@@ -131,213 +131,145 @@
<button type="submit">Sauvegarder</button>
<div id="status-autorole-vocal-form"></div>
</form>
<form id="level-form">
<label>
<input type="checkbox" id="level-enabled" />
Activer le système de niveaux
</label>
<label>
<input type="checkbox" id="level-announcement-enabled" />
Annoncer les améliorations de niveau dans un canal
</label>
<label>
Canal d'affichage des améliorations de niveau :
<br />
<select id="level-announcements-channel">
</select>
</label>
<label>
Message d'amélioration de niveau :
<br />
<textarea
id="level-announcements-message"
rows="4"
cols="50"
placeholder="Ex : Félicitations {user}, tu es maintenant niveau {level} ! 🎉"
></textarea>
</label>
<small>
Variables disponibles :
<ul>
<li><code>{user}</code> → nom de l'utilisateur</li>
<li><code>{mention}</code> → mention de l'utilisateur</li>
<li><code>{level}</code> → niveau atteint</li>
<li><code>{level-xp}</code> → points d'expérience du niveau</li>
</ul>
</small>
<label>
Courbe d'expérience des niveaux :
<br />
<select id="level-xp-curve-type">
<option value="constante">Constante</option>
<option value="linear">Linéaire</option>
<option value="quadratic">Quadratique</option>
<option value="exponential">Exponentielle</option>
</select>
</label>
<label>
Multiplicateur de points d'expérience par niveau :
<br />
<input type="number" id="level-xp-multiplier" min="1" value="1" />
</label>
<label>
Annonce des niveaux tout les X niveaux :
<br />
<input type="number" id="level-announcement-every" min="1" value="1" />
</label>
<label>
Niveau maximum (0 pour infini) :
<br />
<input type="number" id="level-max-level" min="0" value="0" />
</label>
<label>
Rôle avec/sans expérience :
<br />
<select id="level-role-with-or-without-xp-type">
<option value="with">Avec</option>
<option value="without">Sans</option>
</select>
<br />
<select id="level-role-with-or-without-xp" multiple size="5">
</select>
</label>
<label>
Salon avec/sans expérience :
<br />
<select id="level-channel-with-or-without-xp-type">
<option value="with">Avec</option>
<option value="without">Sans</option>
</select>
<br />
<select id="level-channel-with-or-without-xp" multiple size="5">
</select>
</label>
<label>
<input type="checkbox" id="message-xp-enabled" />
Gagner de l'expérience en envoyant des messages
</label>
<label>
Gain de points d'expérience par message :
<br />
Entre
<input type="number" id="level-xp-per-message-min" min="1" value="15" />
et
<input type="number" id="level-xp-per-message-max" min="1" value="25" />
points d'expérience
</label>
<label>
Cooldown entre les messages pour gagner de l'XP (en secondes) :
<br />
<input type="number" id="level-xp-cooldown" min="0" value="2" />
</label>
<label>
<input type="checkbox" id="voice-xp-enabled" />
Gagner de l'expérience en étant en vocal
</label>
<label>
Gain de points d'expérience par minute en vocal :
<br />
Entre
<input type="number" id="level-xp-per-voice-min" min="1" value="10" />
et
<input type="number" id="level-xp-per-voice-max" min="1" value="20" />
points d'expérience
</label>
<button type="submit">Sauvegarder</button>
<div id="status-level-form"></div>
</form>
<script>
const guildId = window.location.pathname.split("/")[2]; // récupère l'ID du serveur
// Afficher le nom du serveur
fetch("/api/guilds")
.then((res) => res.json())
.then((guilds) => {
const guild = guilds.find((g) => g.id === guildId);
if (!guild) {
document.getElementById("guild-name").textContent =
"Serveur introuvable ou accès refusé";
return;
}
document.getElementById(
"guild-name"
).textContent = `Dashboard : ${guild.name}`;
});
fetch(`/api/bot/get-text-channels/${guildId}`)
.then(res => res.json())
.then(channels => {
const selectWelcome = document.getElementById("welcome-channel");
const selectGoodbye = document.getElementById("goodbye-channel");
channels.forEach(channel => {
const option = document.createElement("option");
option.value = channel.id;
option.textContent = `#${channel.name}`;
selectWelcome.appendChild(option);
selectGoodbye.appendChild(option.cloneNode(true));
});
});
const selectExclude = document.getElementById("autorole-vocal-exclude-channel");
selectExclude.innerHTML = ""; // reset
fetch(`/api/bot/get-voice-channels/${guildId}`)
.then(res => res.json())
.then(channels => {
channels.forEach(channel => {
const option = document.createElement("option");
option.value = channel.id;
option.textContent = `#${channel.name}`;
selectExclude.appendChild(option);
});
return fetch(`/api/bot/get-autorole-vocal-config/${guildId}`);
})
.then(res => res.json())
.then(cfg => {
document.getElementById("autorole-vocal-enabled").checked = !!cfg.enabled;
document.getElementById("autorole-vocal-role").value = cfg.roleId ?? "";
const excluded = Array.isArray(cfg.excludeChannelIds)
? cfg.excludeChannelIds.map(String) // sécurité
: [];
Array.from(selectExclude.options).forEach(option => {
option.selected = excluded.includes(option.value);
});
})
.catch(console.error);
fetch(`/api/bot/get-roles/${guildId}`)
.then(res => res.json())
.then(roles => {
const selectAutorole = document.getElementById("autorole-role");
const selectAutoroleVocal = document.getElementById("autorole-vocal-role");
roles.forEach(role => {
const option = document.createElement("option");
option.value = role.id;
option.textContent = role.name;
selectAutorole.appendChild(option);
selectAutoroleVocal.appendChild(option.cloneNode(true));
});
});
const welcomeForm = document.getElementById("welcome-form");
welcomeForm.addEventListener("submit", async (e) => {
e.preventDefault();
const enabled = document.getElementById("welcome-enabled").checked;
const channelId = document.getElementById("welcome-channel").value;
const message = document.getElementById("welcome-message").value;
const res = await fetch("/api/bot/save-welcome-config", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
guildId,
channelId,
welcomeMessage: message,
welcomeEnabled: enabled,
}),
});
const data = await res.json();
document.getElementById("status-welcome-form").textContent = data.success
? "Config de bienvenue sauvegardée ✅"
: "Erreur ❌";
});
const goodbyeForm = document.getElementById("goodbye-form");
goodbyeForm.addEventListener("submit", async (e) => {
e.preventDefault();
const enabled = document.getElementById("goodbye-enabled").checked;
const channelId = document.getElementById("goodbye-channel").value;
const message = document.getElementById("goodbye-message").value;
const res = await fetch("/api/bot/save-goodbye-config", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
guildId,
channelId,
goodbyeMessage: message,
goodbyeEnabled: enabled,
}),
});
const data = await res.json();
document.getElementById("status-goodbye-form").textContent = data.success
? "Config d'au revoir sauvegardée ✅"
: "Erreur ❌";
});
const autoroleNewUserForm = document.getElementById("autorole-newuser-form");
autoroleNewUserForm.addEventListener("submit", async (e) => {
e.preventDefault();
const enabled = document.getElementById("autorole-enabled").checked;
const roleId = document.getElementById("autorole-role").value;
const res = await fetch("/api/bot/save-autorole-newuser-config", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
guildId,
roleId,
enabled,
}),
});
const data = await res.json();
document.getElementById("status-autorole-form").textContent = data.success
? "Config d'auto-rôle pour nouveaux membres sauvegardée ✅"
: "Erreur ❌";
});
const autoroleVocalForm = document.getElementById("autorole-vocal-form");
autoroleVocalForm.addEventListener("submit", async (e) => {
e.preventDefault();
const enabled = document.getElementById("autorole-vocal-enabled").checked;
const roleId = document.getElementById("autorole-vocal-role").value;
const excludeChannelSelect = document.getElementById("autorole-vocal-exclude-channel");
const excludeChannelId = Array.from(excludeChannelSelect.selectedOptions).map(option => option.value);
const res = await fetch("/api/bot/save-autorole-vocal-config", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
guildId,
roleId,
excludeChannelId,
enabled,
}),
});
const data = await res.json();
document.getElementById("status-autorole-vocal-form").textContent = data.success
? "Config d'auto-rôle pour membres en vocal sauvegardée ✅"
: "Erreur ❌";
});
fetch(`/api/bot/get-welcome-config/${guildId}`)
.then(res => res.json())
.then(cfg => {
document.getElementById("welcome-enabled").checked = cfg.enabled;
document.getElementById("welcome-channel").value = cfg.channelId;
document.getElementById("welcome-message").value = cfg.message;
});
fetch(`/api/bot/get-goodbye-config/${guildId}`)
.then(res => res.json())
.then(cfg => {
document.getElementById("goodbye-enabled").checked = cfg.enabled;
document.getElementById("goodbye-channel").value = cfg.channelId;
document.getElementById("goodbye-message").value = cfg.message;
});
fetch(`/api/bot/get-autorole-newuser-config/${guildId}`)
.then(res => res.json())
.then(cfg => {
document.getElementById("autorole-enabled").checked = cfg.enabled;
document.getElementById("autorole-role").value = cfg.roleId;
});
</script>
<script src="/guild/guildBase.js"></script>
<script src="/guild/welcomeForm.js"></script>
<script src="/guild/goodbyeForm.js"></script>
<script src="/guild/autoroleNewUserForm.js"></script>
<script src="/guild/autoroleVocalForm.js"></script>
<script src="/guild/levelForm.js"></script>
</body>
</html>
+29
View File
@@ -0,0 +1,29 @@
const autoroleNewUserForm = document.getElementById("autorole-newuser-form");
const autoroleEnabled = document.getElementById("autorole-enabled");
const autoroleRole = document.getElementById("autorole-role");
const statusAutoroleForm = document.getElementById("status-autorole-form");
fetch(`/api/bot/get-autorole-newuser-config/${guildId}`)
.then(res => res.json())
.then(cfg => {
autoroleEnabled.checked = cfg.enabled;
autoroleRole.value = cfg.roleId;
});
autoroleNewUserForm.addEventListener("submit", async e => {
e.preventDefault();
const res = await fetch("/api/bot/save-autorole-newuser-config", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
guildId,
enabled: autoroleEnabled.checked,
roleId: autoroleRole.value
})
});
statusAutoroleForm.textContent = (await res.json()).success
? "Auto-rôle sauvegardé ✅"
: "Erreur ❌";
});
+43
View File
@@ -0,0 +1,43 @@
const autoroleVocalForm = document.getElementById("autorole-vocal-form");
const autoroleVocalEnabled = document.getElementById("autorole-vocal-enabled");
const autoroleVocalRole = document.getElementById("autorole-vocal-role");
const excludeSelect = document.getElementById("autorole-vocal-exclude-channel");
const statusAutoroleVocalForm = document.getElementById("status-autorole-vocal-form");
fetch(`/api/bot/get-voice-channels/${guildId}`)
.then(res => res.json())
.then(channels => {
channels.forEach(c => {
excludeSelect.appendChild(new Option(`#${c.name}`, c.id));
});
return fetch(`/api/bot/get-autorole-vocal-config/${guildId}`);
})
.then(res => res.json())
.then(cfg => {
autoroleVocalEnabled.checked = cfg.enabled;
autoroleVocalRole.value = cfg.roleId;
Array.from(excludeSelect.options).forEach(opt => {
opt.selected = cfg.excludeChannelIds?.includes(opt.value);
});
});
autoroleVocalForm.addEventListener("submit", async e => {
e.preventDefault();
const exclude = Array.from(excludeSelect.selectedOptions).map(o => o.value);
const res = await fetch("/api/bot/save-autorole-vocal-config", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
guildId,
enabled: autoroleVocalEnabled.checked,
roleId: autoroleVocalRole.value,
excludeChannelId: exclude
})
});
statusAutoroleVocalForm.textContent = (await res.json()).success
? "Auto-rôle vocal sauvegardé ✅"
: "Erreur ❌";
});
+32
View File
@@ -0,0 +1,32 @@
const goodbyeForm = document.getElementById("goodbye-form");
const goodbyeEnabled = document.getElementById("goodbye-enabled");
const goodbyeChannel = document.getElementById("goodbye-channel");
const goodbyeMessage = document.getElementById("goodbye-message");
const statusGoodbyeForm = document.getElementById("status-goodbye-form");
fetch(`/api/bot/get-goodbye-config/${guildId}`)
.then(res => res.json())
.then(cfg => {
goodbyeEnabled.checked = cfg.enabled;
goodbyeChannel.value = cfg.channelId;
goodbyeMessage.value = cfg.message;
});
goodbyeForm.addEventListener("submit", async e => {
e.preventDefault();
const res = await fetch("/api/bot/save-goodbye-config", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
guildId,
goodbyeEnabled: goodbyeEnabled.checked,
channelId: goodbyeChannel.value,
goodbyeMessage: goodbyeMessage.value
})
});
statusGoodbyeForm.textContent = (await res.json()).success
? "Config au revoir sauvegardée ✅"
: "Erreur ❌";
});
+40
View File
@@ -0,0 +1,40 @@
window.guildId = window.location.pathname.split("/")[2];
// Nom du serveur
fetch("/api/guilds")
.then(res => res.json())
.then(guilds => {
const guild = guilds.find(g => g.id === guildId);
document.getElementById("guild-name").textContent =
guild ? `Dashboard : ${guild.name}` : "Serveur introuvable";
});
// Channels texte
fetch(`/api/bot/get-text-channels/${guildId}`)
.then(res => res.json())
.then(channels => {
const welcome = document.getElementById("welcome-channel");
const goodbye = document.getElementById("goodbye-channel");
const levelAnnouncements = document.getElementById("level-announcements-channel");
channels.forEach(c => {
const opt = new Option(`#${c.name}`, c.id);
welcome?.appendChild(opt);
goodbye?.appendChild(opt.cloneNode(true));
levelAnnouncements?.appendChild(opt.cloneNode(true));
});
});
// Rôles
fetch(`/api/bot/get-roles/${guildId}`)
.then(res => res.json())
.then(roles => {
const newUser = document.getElementById("autorole-role");
const vocal = document.getElementById("autorole-vocal-role");
roles.forEach(r => {
const opt = new Option(r.name, r.id);
newUser?.appendChild(opt);
vocal?.appendChild(opt.cloneNode(true));
});
});
+119
View File
@@ -0,0 +1,119 @@
const levelForm = document.getElementById("level-form");
const levelEnabled = document.getElementById("level-enabled");
const levelAnnouncementsEnabled = document.getElementById("level-announcement-enabled");
const levelAnnouncementsChannel = document.getElementById("level-announcements-channel");
const levelAnnouncementsMessage = document.getElementById("level-announcements-message");
const xpCourbeType = document.getElementById("level-xp-curve-type");
const multiplierCourbeForLevel = document.getElementById("level-xp-multiplier");
const levelAnnouncementEveryLevel = document.getElementById("level-announcement-every");
const levelMax = document.getElementById("level-max-level");
const roleWithWithoutType = document.getElementById("level-role-with-or-without-xp-type");
const roleWithWithoutXp = document.getElementById("level-role-with-or-without-xp");
const salonWithWithoutType = document.getElementById("level-channel-with-or-without-xp-type");
const salonWithWithoutXp = document.getElementById("level-channel-with-or-without-xp");
const gainXpOnMessage = document.getElementById("message-xp-enabled");
const gainXpMessageLowerBound = document.getElementById("level-xp-per-message-min");
const gainXpMessageUpperBound = document.getElementById("level-xp-per-message-max");
const cooldownXpMessageSeconds = document.getElementById("level-xp-cooldown");
const gainXpOnVoice = document.getElementById("voice-xp-enabled");
const gainVoiceXpLowerBound = document.getElementById("level-xp-per-voice-min");
const gainVoiceXpUpperBound = document.getElementById("level-xp-per-voice-max");
const statusLevelForm = document.getElementById("status-level-form");
// 1️⃣ RÔLES
fetch(`/api/bot/get-roles/${guildId}`)
.then(res => res.json())
.then(roles => {
roles.forEach(r => {
roleWithWithoutXp?.appendChild(new Option(r.name, r.id));
});
// 2️⃣ SALONS TEXTE
return fetch(`/api/bot/get-text-channels/${guildId}`);
})
.then(res => res.json())
.then(textSalons => {
textSalons.forEach(c => {
salonWithWithoutXp?.appendChild(new Option(`#${c.name}`, c.id));
});
// 3️⃣ SALONS VOCAUX
return fetch(`/api/bot/get-voice-channels/${guildId}`);
})
.then(res => res.json())
.then(voiceSalons => {
voiceSalons.forEach(c => {
salonWithWithoutXp?.appendChild(new Option(`🔊 ${c.name}`, c.id));
});
// 4️⃣ CONFIG (APRÈS QUE TOUT EST CHARGÉ)
return fetch(`/api/bot/get-level-config/${guildId}`);
})
.then(res => res.json())
.then(cfg => {
levelEnabled.checked = cfg.enabled;
levelAnnouncementsEnabled.checked = cfg.levelAnnouncementsEnabled;
levelAnnouncementsChannel.value = cfg.levelAnnouncementsChannelId;
levelAnnouncementsMessage.value = cfg.levelAnnouncementsMessage;
xpCourbeType.value = cfg.xpCourbeType;
multiplierCourbeForLevel.value = cfg.multiplierCourbeForLevel;
levelAnnouncementEveryLevel.value = cfg.levelAnnouncementEveryLevel;
levelMax.value = cfg.levelMax;
roleWithWithoutType.value = cfg.roleWithWithoutType;
Array.from(roleWithWithoutXp.options).forEach(opt => {
opt.selected = cfg.roleWithWithoutXp?.includes(opt.value);
});
salonWithWithoutType.value = cfg.salonWithWithoutType;
Array.from(salonWithWithoutXp.options).forEach(opt => {
opt.selected = cfg.salonWithWithoutXp?.includes(opt.value);
});
gainXpOnMessage.checked = cfg.gainXpOnMessage;
gainXpMessageLowerBound.value = cfg.gainXpMessageLowerBound;
gainXpMessageUpperBound.value = cfg.gainXpMessageUpperBound;
cooldownXpMessageSeconds.value = cfg.cooldownXpMessageSeconds;
gainXpOnVoice.checked = cfg.gainXpOnVoice;
gainVoiceXpLowerBound.value = cfg.gainVoiceXpLowerBound;
gainVoiceXpUpperBound.value = cfg.gainVoiceXpUpperBound;
})
.catch(console.error);
levelForm.addEventListener("submit", async e => {
e.preventDefault();
const res = await fetch("/api/bot/save-level-config", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
guildId,
levelEnabled: levelEnabled.checked,
levelAnnouncementsEnabled: levelAnnouncementsEnabled.checked,
levelAnnouncementsChannelId: levelAnnouncementsChannel.value,
levelAnnouncementsMessage: levelAnnouncementsMessage.value,
xpCourbeType: xpCourbeType.value,
multiplierCourbeForLevel: parseInt(multiplierCourbeForLevel.value, 10),
levelAnnouncementEveryLevel: parseInt(levelAnnouncementEveryLevel.value, 10),
levelMax: parseInt(levelMax.value, 10),
roleWithWithoutType: roleWithWithoutType.value,
roleWithWithoutXp: Array.from(roleWithWithoutXp.selectedOptions).map(opt => opt.value),
salonWithWithoutType: salonWithWithoutType.value,
salonWithWithoutXp: Array.from(salonWithWithoutXp.selectedOptions).map(opt => opt.value),
gainXpOnMessage: gainXpOnMessage.checked,
gainXpMessageLowerBound: parseInt(gainXpMessageLowerBound.value, 10),
gainXpMessageUpperBound: parseInt(gainXpMessageUpperBound.value, 10),
cooldownXpMessageSeconds: parseInt(cooldownXpMessageSeconds.value, 10),
gainXpOnVoice: gainXpOnVoice.checked,
gainVoiceXpLowerBound: parseInt(gainVoiceXpLowerBound.value, 10),
gainVoiceXpUpperBound: parseInt(gainVoiceXpUpperBound.value, 10)
})
});
statusLevelForm.textContent = (await res.json()).success
? "Config niveaux sauvegardée ✅"
: "Erreur ❌";
});
+32
View File
@@ -0,0 +1,32 @@
const welcomeForm = document.getElementById("welcome-form");
const welcomeEnabled = document.getElementById("welcome-enabled");
const welcomeChannel = document.getElementById("welcome-channel");
const welcomeMessage = document.getElementById("welcome-message");
const statusWelcomeForm = document.getElementById("status-welcome-form");
fetch(`/api/bot/get-welcome-config/${guildId}`)
.then(res => res.json())
.then(cfg => {
welcomeEnabled.checked = cfg.enabled;
welcomeChannel.value = cfg.channelId;
welcomeMessage.value = cfg.message;
});
welcomeForm.addEventListener("submit", async e => {
e.preventDefault();
const res = await fetch("/api/bot/save-welcome-config", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
guildId,
welcomeEnabled: welcomeEnabled.checked,
channelId: welcomeChannel.value,
welcomeMessage: welcomeMessage.value
})
});
statusWelcomeForm.textContent = (await res.json()).success
? "Config bienvenue sauvegardée ✅"
: "Erreur ❌";
});