Version 2.1.0 - Update UI
This commit is contained in:
18 files changed
+3508
-330
No files matched your search
+121
-15
@@ -19,6 +19,7 @@ const players = require('../player/Player')
|
||||
const discordBot = require('../discord/Bot')
|
||||
const serverSettings = require('../discord/ServerSettings')
|
||||
const playlists = require('../playlists/PlaylistManager')
|
||||
const guildPlaylists = require('../playlists/GuildPlaylists')
|
||||
const warp = require('../utils/WarpProxy')
|
||||
const SafeMetric = require('../utils/SafeMetric')
|
||||
const LogReader = require('../utils/LogReader')
|
||||
@@ -53,7 +54,9 @@ const SPOTIFY_TEST_TIMEOUT = 15 * 1000
|
||||
// Changement des processus yt-dlp actifs (Youtube.js ne prévient pas le player) : sondage léger
|
||||
const YTDLP_PROCESS_POLL = 2000
|
||||
// Jamais "config" : contient le token du bot et les secrets
|
||||
const DATABASES = ["Users", "Playlists", "history", "media", "previous", "server_settings"]
|
||||
const DATABASES = ["Users", "Playlists", "GuildPlaylists", "history", "media", "previous", "server_settings"]
|
||||
// Playlists partagées : qui peut les modifier (tous les membres / modérateurs)
|
||||
const SHARED_PLAYLISTS_EDITORS = ["members", "mods"]
|
||||
const PLAYER_ACTIONS = ["pause", "resume", "skip", "previous", "stop", "leave", "loop", "shuffle", "clear"]
|
||||
const LOG_LEVELS = ["INFO", "WARN", "ERROR", "FATAL"]
|
||||
const USER_FILTERS = ["all", "online", "admin", "banned", "deleted"]
|
||||
@@ -447,7 +450,42 @@ function guildSummary(guildData, ctx = buildContext()) {
|
||||
missingPermissions,
|
||||
isMediaGuild: !!id && id === configuration.getMediaGuildId(),
|
||||
player: isConnectedPlayer(player) ? playerSummary(player) : null,
|
||||
stats: guildStats(id)
|
||||
stats: guildStats(id),
|
||||
sharedPlaylists: sharedPlaylistsSummary(id)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Option des playlists partagées d'un serveur + nombre de playlists enregistrées (même masquées)
|
||||
*/
|
||||
function sharedPlaylistsSummary(guildId) {
|
||||
const settings = typeof serverSettings.getSharedPlaylists === "function"
|
||||
? serverSettings.getSharedPlaylists(guildId)
|
||||
: { enabled: false, editors: "members" }
|
||||
return {
|
||||
enabled: !!settings?.enabled,
|
||||
editors: SHARED_PLAYLISTS_EDITORS.includes(settings?.editors) ? settings.editors : "members",
|
||||
count: guildPlaylists.countPlaylists(guildId)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Résumé d'une playlist pour les listes d'administration (jamais la liste des titres)
|
||||
*/
|
||||
function playlistLite(p) {
|
||||
return {
|
||||
playlistId: p?.playlistId != null ? String(p.playlistId) : null,
|
||||
title: p?.title ?? null,
|
||||
type: p?.type ?? null,
|
||||
// "google" : synchronisée depuis le compte Google du propriétaire
|
||||
source: p?.source ?? null,
|
||||
url: typeof p?.url === "string" ? p.url : null,
|
||||
thumbnail: typeof p?.thumbnail === "string" ? p.thumbnail : null,
|
||||
duration: safeNum(p?.duration),
|
||||
syncedAt: toIso(p?.syncedAt),
|
||||
createdAt: toIso(p?.createdAt),
|
||||
updatedAt: toIso(p?.updatedAt),
|
||||
songCount: Array.isArray(p?.songs) ? p.songs.length : 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1601,6 +1639,27 @@ function attach({ io: ioInstance, socket, socketUser }) {
|
||||
return { secureRole: role ? { id: role.id, name: role.name } : null }
|
||||
})
|
||||
|
||||
// Playlists partagées d'un serveur : {guildId, enabled?, editors?} (désactiver masque sans rien supprimer)
|
||||
AdminRequest("/ADMIN/GUILDS/SET_SHARED_PLAYLISTS", async (payload, me) => {
|
||||
const data = asObject(payload)
|
||||
const guildId = requireId(data.guildId, "guildId")
|
||||
if (!discordBot.getGuilds().has(guildId)) throw new AdminError("NOT_FOUND", "Serveur introuvable")
|
||||
const patch = {}
|
||||
if (data.enabled !== undefined) patch.enabled = requireBool(data.enabled, "enabled")
|
||||
if (data.editors !== undefined) {
|
||||
if (!SHARED_PLAYLISTS_EDITORS.includes(data.editors)) throw new AdminError("INVALID_PARAMS", "Paramètre \"editors\" invalide (members ou mods)")
|
||||
patch.editors = data.editors
|
||||
}
|
||||
if (Object.keys(patch).length === 0) throw new AdminError("INVALID_PARAMS", "Aucune modification demandée (enabled ou editors)")
|
||||
const settings = serverSettings.setSharedPlaylists(guildId, patch)
|
||||
if (!settings) throw new AdminError("INVALID_PARAMS", "Réglage des playlists partagées invalide")
|
||||
audit(me, "playlists partagées", `${guildId} -> ${settings.enabled ? "activées" : "désactivées"}, modification : ${settings.editors}`)
|
||||
process.emit("GUILDS_UPDATE")
|
||||
// Les membres présents sur ce serveur rechargent leurs playlists partagées
|
||||
process.emit("GUILD_PLAYLISTS_UPDATE", guildId)
|
||||
return { sharedPlaylists: sharedPlaylistsSummary(guildId) }
|
||||
})
|
||||
|
||||
AdminRequest("/ADMIN/GUILDS/REFRESH", async (payload, me) => {
|
||||
if (typeof discordBot.refreshGuilds !== "function") throw new AdminError("UNAVAILABLE", "Rafraîchissement indisponible")
|
||||
await discordBot.refreshGuilds()
|
||||
@@ -1763,27 +1822,63 @@ function attach({ io: ioInstance, socket, socketUser }) {
|
||||
username: owner?.identity?.username ?? null,
|
||||
// Identité légère pour l'affichage (jamais auth ni tokens)
|
||||
owner: owner ? { ...userLite(owner.identity), isAdmin: owner.labels?.includes("ADMIN") ?? false, isDeleted: owner.labels?.includes("DELETED") ?? false } : null,
|
||||
playlists: list.map(p => ({
|
||||
playlistId: p?.playlistId != null ? String(p.playlistId) : null,
|
||||
title: p?.title ?? null,
|
||||
type: p?.type ?? null,
|
||||
// "google" : synchronisée depuis le compte Google du propriétaire
|
||||
source: p?.source ?? null,
|
||||
url: typeof p?.url === "string" ? p.url : null,
|
||||
thumbnail: typeof p?.thumbnail === "string" ? p.thumbnail : null,
|
||||
duration: safeNum(p?.duration),
|
||||
syncedAt: toIso(p?.syncedAt),
|
||||
songCount: Array.isArray(p?.songs) ? p.songs.length : 0
|
||||
}))
|
||||
playlists: list.map(playlistLite)
|
||||
})
|
||||
}
|
||||
return result
|
||||
})
|
||||
|
||||
// Playlists partagées des serveurs : {guildId?}. Serveurs ayant des playlists (même désactivées ou quittés
|
||||
// par le bot) ou l'option activée
|
||||
AdminRequest("/ADMIN/PLAYLISTS/GUILDS", async (payload) => {
|
||||
const data = asObject(payload)
|
||||
const filter = data.guildId === undefined || data.guildId === null ? null : requireId(data.guildId, "guildId")
|
||||
const botGuilds = discordBot.getGuilds()
|
||||
const ids = new Set(guildPlaylists.listGuildIds())
|
||||
if (typeof serverSettings.getSharedPlaylists === "function") {
|
||||
for (const guildId of botGuilds.keys()) {
|
||||
if (serverSettings.getSharedPlaylists(guildId)?.enabled) ids.add(guildId)
|
||||
}
|
||||
}
|
||||
if (filter) {
|
||||
ids.clear()
|
||||
ids.add(filter)
|
||||
}
|
||||
const result = []
|
||||
for (const guildId of ids) {
|
||||
const list = guildPlaylists.getPlaylists(guildId)
|
||||
const shared = sharedPlaylistsSummary(guildId)
|
||||
if (!filter && list.length === 0 && !shared.enabled) continue
|
||||
const guild = botGuilds.get(guildId)
|
||||
result.push({
|
||||
guildId,
|
||||
guildName: guild?.name ?? null,
|
||||
guildIcon: guild?.icon ?? null,
|
||||
settings: { enabled: shared.enabled, editors: shared.editors },
|
||||
playlists: list.map(p => ({
|
||||
...playlistLite(p),
|
||||
createdBy: p?.createdBy ? userLite(p.createdBy) : null
|
||||
}))
|
||||
})
|
||||
}
|
||||
return result.sort((a, b) => (a.guildName || a.guildId).localeCompare(b.guildName || b.guildId))
|
||||
})
|
||||
|
||||
// {userId, playlistId} : playlist personnelle ; {guildId, playlistId} : playlist partagée d'un serveur
|
||||
AdminRequest("/ADMIN/PLAYLISTS/DELETE", async (payload, me) => {
|
||||
const data = asObject(payload)
|
||||
const userId = requireId(data.userId)
|
||||
const playlistId = requireString(typeof data.playlistId === "number" ? String(data.playlistId) : data.playlistId, "playlistId", 1, 64)
|
||||
if (data.guildId !== undefined && data.guildId !== null) {
|
||||
const guildId = requireId(data.guildId, "guildId")
|
||||
const playlist = guildPlaylists.getPlaylist(guildId, playlistId)
|
||||
if (!playlist) throw new AdminError("NOT_FOUND", "Playlist introuvable")
|
||||
if (!guildPlaylists.deletePlaylist(guildId, playlist.playlistId)) throw new AdminError("NOT_FOUND", "Playlist introuvable")
|
||||
audit(me, "suppression de la playlist partagée", `"${playlist.title}" (${playlistId}) du serveur ${guildId}`)
|
||||
process.emit("GUILDS_UPDATE")
|
||||
process.emit("GUILD_PLAYLISTS_UPDATE", guildId)
|
||||
return { deleted: true }
|
||||
}
|
||||
const userId = requireId(data.userId)
|
||||
const list = dbData("Playlists", {})[userId]
|
||||
const playlist = Array.isArray(list) ? list.find(p => p?.playlistId != null && String(p.playlistId) === playlistId) : null
|
||||
if (!playlist) throw new AdminError("NOT_FOUND", "Playlist introuvable")
|
||||
@@ -2016,6 +2111,17 @@ function init(ioInstance, deps = {}) {
|
||||
}, 500)
|
||||
process.on("GUILDS_UPDATE", pushGuilds)
|
||||
|
||||
// Playlists modifiées (personnelles ou partagées) : la section Playlists ouverte se relit d'elle-même
|
||||
const changedScopes = new Set()
|
||||
const pushPlaylists = debounce(() => {
|
||||
const scopes = [...changedScopes]
|
||||
changedScopes.clear()
|
||||
if (roomSize(ADMIN_ROOM) === 0) return
|
||||
io.to(ADMIN_ROOM).emit("/ADMIN/PLAYLISTS/UPDATE", { scopes })
|
||||
}, 800)
|
||||
process.on("PLAYLISTS_UPDATE", () => { changedScopes.add("personal"); pushPlaylists() })
|
||||
process.on("GUILD_PLAYLISTS_UPDATE", () => { changedScopes.add("guild"); pushPlaylists() })
|
||||
|
||||
// WARP : changement d'état du proxy
|
||||
process.on("WARP_UPDATE", () => {
|
||||
if (roomSize(ADMIN_ROOM) === 0) return
|
||||
|
||||
Reference in new issue
Block a user