local QBCore = exports['qb-core']:GetCoreObject() -- Aktive DJ-Booths local activeDJBooths = {} -- Database Setup CreateThread(function() MySQL.query([[ CREATE TABLE IF NOT EXISTS dj_playlists ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) NOT NULL, owner VARCHAR(50) NOT NULL, description TEXT DEFAULT NULL, is_public TINYINT(1) DEFAULT 0, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ]]) MySQL.query([[ CREATE TABLE IF NOT EXISTS dj_playlist_songs ( id INT AUTO_INCREMENT PRIMARY KEY, playlist_id INT NOT NULL, title VARCHAR(255) NOT NULL, artist VARCHAR(255) DEFAULT NULL, url TEXT NOT NULL, converted_url TEXT DEFAULT NULL, duration INT DEFAULT NULL, position INT DEFAULT 0, song_type VARCHAR(20) DEFAULT 'direct', thumbnail TEXT DEFAULT NULL, added_by VARCHAR(50) DEFAULT NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (playlist_id) REFERENCES dj_playlists(id) ON DELETE CASCADE ) ]]) MySQL.query([[ CREATE TABLE IF NOT EXISTS dj_url_cache ( id INT AUTO_INCREMENT PRIMARY KEY, original_url VARCHAR(500) NOT NULL, converted_url TEXT NOT NULL, video_id VARCHAR(50) DEFAULT NULL, title VARCHAR(255) DEFAULT NULL, duration INT DEFAULT NULL, thumbnail TEXT DEFAULT NULL, expires_at TIMESTAMP NOT NULL, hit_count INT DEFAULT 1, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, last_accessed TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY (original_url(255)) ) ]]) MySQL.query([[ CREATE TABLE IF NOT EXISTS dj_session_history ( id INT AUTO_INCREMENT PRIMARY KEY, dj_citizenid VARCHAR(50) NOT NULL, dj_name VARCHAR(100) NOT NULL, booth_name VARCHAR(100) NOT NULL, song_title VARCHAR(255) NOT NULL, song_url TEXT NOT NULL, song_type VARCHAR(20) DEFAULT 'direct', volume INT DEFAULT 50, duration_played INT DEFAULT NULL, session_start TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, session_end TIMESTAMP NULL DEFAULT NULL, listeners_count INT DEFAULT 0 ) ]]) end) -- YouTube URL Funktionen local function extractYouTubeVideoId(url) -- YouTube URL Patterns local patterns = { "youtube%.com/watch%?v=([%w%-_]+)", "youtube%.com/watch%?.*&v=([%w%-_]+)", "youtu%.be/([%w%-_]+)", "youtube%.com/embed/([%w%-_]+)" } for _, pattern in ipairs(patterns) do local videoId = string.match(url, pattern) if videoId then return videoId end end return nil end local function cleanYouTubeUrl(url) local videoId = extractYouTubeVideoId(url) if not videoId then return nil end -- Erstelle saubere YouTube URL ohne Playlist-Parameter return "https://www.youtube.com/watch?v=" .. videoId end -- Events RegisterServerEvent('dj:server:playMusic') AddEventHandler('dj:server:playMusic', function(data) local src = source local Player = QBCore.Functions.GetPlayer(src) if not Player then return end -- Speichere den aktuellen DJ-Status activeDJBooths[data.booth.name] = { url = data.url, title = data.title, volume = data.volume, range = data.range, dj = { id = Player.PlayerData.citizenid, name = Player.PlayerData.charinfo.firstname .. ' ' .. Player.PlayerData.charinfo.lastname }, coords = data.booth.coords } -- Sende an alle Spieler TriggerClientEvent('dj:client:playMusic', -1, data) -- Log für Admin print(('[DJ System] %s spielt Musik ab: %s | Booth: %s'):format( GetPlayerName(src), data.title, data.booth.name )) -- Speichere in Datenbank MySQL.insert('INSERT INTO dj_session_history (dj_citizenid, dj_name, booth_name, song_title, song_url, song_type, volume, session_start) VALUES (?, ?, ?, ?, ?, ?, ?, NOW())', { Player.PlayerData.citizenid, Player.PlayerData.charinfo.firstname .. ' ' .. Player.PlayerData.charinfo.lastname, data.booth.name, data.title, data.url, string.match(data.url, "youtube") and 'youtube' or 'direct', data.volume }) end) RegisterServerEvent('dj:server:stopMusic') AddEventHandler('dj:server:stopMusic', function(boothName) local src = source -- Entferne DJ-Status if activeDJBooths[boothName] then -- Aktualisiere Session-Ende in der Datenbank local djData = activeDJBooths[boothName] if djData and djData.dj then MySQL.update('UPDATE dj_session_history SET session_end = NOW() WHERE dj_citizenid = ? AND booth_name = ? AND session_end IS NULL', { djData.dj.id, boothName }) end activeDJBooths[boothName] = nil end -- Sende an alle Spieler TriggerClientEvent('dj:client:stopMusic', -1, boothName) print(('[DJ System] %s hat die Musik gestoppt in: %s'):format(GetPlayerName(src), boothName)) end) RegisterServerEvent('dj:server:setVolume') AddEventHandler('dj:server:setVolume', function(data) local src = source -- Aktualisiere DJ-Status if activeDJBooths[data.booth.name] then activeDJBooths[data.booth.name].volume = data.volume activeDJBooths[data.booth.name].range = data.range end -- Sende an alle Spieler TriggerClientEvent('dj:client:setVolume', -1, data) print(('[DJ System] %s hat die Lautstärke auf %d%% gesetzt in: %s'):format( GetPlayerName(src), data.volume, data.booth.name )) end) -- Playlist Management RegisterServerEvent('dj:server:getPlaylists') AddEventHandler('dj:server:getPlaylists', function() local src = source local Player = QBCore.Functions.GetPlayer(src) if not Player then return end MySQL.query('SELECT * FROM dj_playlists WHERE owner = ? OR is_public = 1', {Player.PlayerData.citizenid}, function(playlists) local playlistData = {} for _, playlist in pairs(playlists) do MySQL.query('SELECT * FROM dj_playlist_songs WHERE playlist_id = ? ORDER BY position', {playlist.id}, function(songs) table.insert(playlistData, { id = playlist.id, name = playlist.name, description = playlist.description, isPublic = playlist.is_public == 1, isOwner = playlist.owner == Player.PlayerData.citizenid, songs = songs }) if #playlistData == #playlists then TriggerClientEvent('dj:client:updatePlaylists', src, playlistData) end end) end if #playlists == 0 then TriggerClientEvent('dj:client:updatePlaylists', src, {}) end end) end) RegisterServerEvent('dj:server:createPlaylist') AddEventHandler('dj:server:createPlaylist', function(name, description, isPublic) local src = source local Player = QBCore.Functions.GetPlayer(src) if not Player then return end MySQL.insert('INSERT INTO dj_playlists (name, owner, description, is_public) VALUES (?, ?, ?, ?)', { name, Player.PlayerData.citizenid, description or '', isPublic and 1 or 0 }, function(id) if id then TriggerClientEvent('QBCore:Notify', src, 'Playlist "' .. name .. '" wurde erstellt!', 'success') TriggerEvent('dj:server:getPlaylists', src) else TriggerClientEvent('QBCore:Notify', src, 'Fehler beim Erstellen der Playlist!', 'error') end end) end) RegisterServerEvent('dj:server:addSongToPlaylist') AddEventHandler('dj:server:addSongToPlaylist', function(playlistId, song) local src = source local Player = QBCore.Functions.GetPlayer(src) if not Player then return end -- Prüfe ob Playlist dem Spieler gehört MySQL.query('SELECT * FROM dj_playlists WHERE id = ? AND (owner = ? OR is_public = 1)', { playlistId, Player.PlayerData.citizenid }, function(result) if result[1] then -- Bereinige YouTube URL local songUrl = song.url if string.match(songUrl, "youtube") then local cleanUrl = cleanYouTubeUrl(songUrl) if cleanUrl then songUrl = cleanUrl end end -- Hole höchste Position MySQL.query('SELECT MAX(position) as max_pos FROM dj_playlist_songs WHERE playlist_id = ?', {playlistId}, function(posResult) local position = 1 if posResult[1] and posResult[1].max_pos then position = posResult[1].max_pos + 1 end -- Füge Song hinzu MySQL.insert('INSERT INTO dj_playlist_songs (playlist_id, title, artist, url, song_type, position, added_by) VALUES (?, ?, ?, ?, ?, ?, ?)', { playlistId, song.title, song.artist or '', songUrl, string.match(songUrl, "youtube") and 'youtube' or 'direct', position, Player.PlayerData.citizenid }, function(id) if id then TriggerClientEvent('QBCore:Notify', src, 'Song wurde zur Playlist hinzugefügt!', 'success') TriggerEvent('dj:server:getPlaylists', src) else TriggerClientEvent('QBCore:Notify', src, 'Fehler beim Hinzufügen des Songs!', 'error') end end) end) else TriggerClientEvent('QBCore:Notify', src, 'Du hast keine Berechtigung für diese Playlist!', 'error') end end) end) RegisterServerEvent('dj:server:removeSongFromPlaylist') AddEventHandler('dj:server:removeSongFromPlaylist', function(playlistId, songId) local src = source local Player = QBCore.Functions.GetPlayer(src) if not Player then return end -- Prüfe ob Playlist dem Spieler gehört MySQL.query('SELECT * FROM dj_playlists WHERE id = ? AND owner = ?', { playlistId, Player.PlayerData.citizenid }, function(result) if result[1] then MySQL.query('DELETE FROM dj_playlist_songs WHERE id = ? AND playlist_id = ?', { songId, playlistId }, function(affectedRows) if affectedRows > 0 then TriggerClientEvent('QBCore:Notify', src, 'Song wurde aus der Playlist entfernt!', 'success') TriggerEvent('dj:server:getPlaylists', src) else TriggerClientEvent('QBCore:Notify', src, 'Fehler beim Entfernen des Songs!', 'error') end end) else TriggerClientEvent('QBCore:Notify', src, 'Du hast keine Berechtigung für diese Playlist!', 'error') end end) end) RegisterServerEvent('dj:server:deletePlaylist') AddEventHandler('dj:server:deletePlaylist', function(playlistId) local src = source local Player = QBCore.Functions.GetPlayer(src) if not Player then return end MySQL.query('DELETE FROM dj_playlists WHERE id = ? AND owner = ?', { playlistId, Player.PlayerData.citizenid }, function(affectedRows) if affectedRows > 0 then TriggerClientEvent('QBCore:Notify', src, 'Playlist wurde gelöscht!', 'success') TriggerEvent('dj:server:getPlaylists', src) else TriggerClientEvent('QBCore:Notify', src, 'Fehler beim Löschen der Playlist!', 'error') end end) end) RegisterServerEvent('dj:server:updatePlaylist') AddEventHandler('dj:server:updatePlaylist', function(playlistId, name, description, isPublic) local src = source local Player = QBCore.Functions.GetPlayer(src) if not Player then return end MySQL.update('UPDATE dj_playlists SET name = ?, description = ?, is_public = ? WHERE id = ? AND owner = ?', { name, description or '', isPublic and 1 or 0, playlistId, Player.PlayerData.citizenid }, function(affectedRows) if affectedRows > 0 then TriggerClientEvent('QBCore:Notify', src, 'Playlist wurde aktualisiert!', 'success') TriggerEvent('dj:server:getPlaylists', src) else TriggerClientEvent('QBCore:Notify', src, 'Fehler beim Aktualisieren der Playlist!', 'error') end end) end) -- Synchronisation RegisterServerEvent('dj:server:requestActiveDJs') AddEventHandler('dj:server:requestActiveDJs', function() local src = source TriggerClientEvent('dj:client:receiveActiveDJs', src, activeDJBooths) end) -- Wenn ein Spieler das Spiel verlässt und DJ war, stoppe die Musik AddEventHandler('playerDropped', function(reason) local src = source local Player = QBCore.Functions.GetPlayer(src) if not Player then return end -- Prüfe ob Spieler DJ war for boothName, boothData in pairs(activeDJBooths) do if boothData.dj and boothData.dj.id == Player.PlayerData.citizenid then -- Spieler war DJ, stoppe Musik -- Aktualisiere Session-Ende in der Datenbank MySQL.update('UPDATE dj_session_history SET session_end = NOW() WHERE dj_citizenid = ? AND booth_name = ? AND session_end IS NULL', { boothData.dj.id, boothName }) activeDJBooths[boothName] = nil TriggerClientEvent('dj:client:stopMusic', -1, boothName) print(('[DJ System] DJ %s hat das Spiel verlassen, Musik in %s gestoppt'):format( GetPlayerName(src), boothName )) end end end) -- Cache Cleanup (läuft alle 30 Minuten) CreateThread(function() while true do Wait(1800000) -- 30 Minuten MySQL.query('DELETE FROM dj_url_cache WHERE expires_at < NOW()') print('[DJ System] Cache bereinigt') end end) -- Initialisiere Standard-Playlists CreateThread(function() Wait(5000) -- Warte bis Datenbank bereit ist -- Prüfe ob Standard-Playlists existieren MySQL.query('SELECT * FROM dj_playlists WHERE owner = ?', {'system'}, function(result) if #result == 0 then print('[DJ System] Erstelle Standard-Playlists') -- Erstelle Standard-Playlists for _, playlist in pairs(Config.DefaultPlaylists) do MySQL.insert('INSERT INTO dj_playlists (name, owner, description, is_public) VALUES (?, ?, ?, ?)', { playlist.name, 'system', 'Standard-Playlist', 1 }, function(playlistId) if playlistId then -- Füge Songs hinzu for position, song in ipairs(playlist.songs) do MySQL.insert('INSERT INTO dj_playlist_songs (playlist_id, title, url, song_type, position, added_by) VALUES (?, ?, ?, ?, ?, ?)', { playlistId, song.title, song.url, string.match(song.url, "youtube") and 'youtube' or 'direct', position, 'system' }) end print('[DJ System] Standard-Playlist erstellt: ' .. playlist.name) end end) end end end) end) -- QBCore Callbacks QBCore.Functions.CreateCallback('dj:server:getActiveDJs', function(source, cb) cb(activeDJBooths) end) QBCore.Functions.CreateCallback('dj:server:getPlaylists', function(source, cb) local Player = QBCore.Functions.GetPlayer(source) if not Player then return cb({}) end MySQL.query('SELECT * FROM dj_playlists WHERE owner = ? OR is_public = 1', {Player.PlayerData.citizenid}, function(playlists) local playlistData = {} local remaining = #playlists if remaining == 0 then return cb({}) end for _, playlist in pairs(playlists) do MySQL.query('SELECT * FROM dj_playlist_songs WHERE playlist_id = ? ORDER BY position', {playlist.id}, function(songs) table.insert(playlistData, { id = playlist.id, name = playlist.name, description = playlist.description, isPublic = playlist.is_public == 1, isOwner = playlist.owner == Player.PlayerData.citizenid, songs = songs }) remaining = remaining - 1 if remaining == 0 then cb(playlistData) end end) end end) end) QBCore.Functions.CreateCallback('dj:server:getSessionHistory', function(source, cb, limit) local Player = QBCore.Functions.GetPlayer(source) if not Player then return cb({}) end limit = limit or 50 MySQL.query('SELECT * FROM dj_session_history WHERE dj_citizenid = ? ORDER BY session_start DESC LIMIT ?', { Player.PlayerData.citizenid, limit }, function(history) cb(history) end) end) -- Admin Commands QBCore.Commands.Add('djstop', 'Stoppe alle DJ-Booths (Admin)', {}, false, function(source, args) local Player = QBCore.Functions.GetPlayer(source) if Player.PlayerData.job.name ~= 'admin' and Player.PlayerData.job.name ~= 'police' then TriggerClientEvent('QBCore:Notify', source, 'Du hast keine Berechtigung für diesen Befehl!', 'error') return end for boothName, _ in pairs(activeDJBooths) do -- Aktualisiere Session-Ende in der Datenbank local djData = activeDJBooths[boothName] if djData and djData.dj then MySQL.update('UPDATE dj_session_history SET session_end = NOW() WHERE dj_citizenid = ? AND booth_name = ? AND session_end IS NULL', { djData.dj.id, boothName }) end activeDJBooths[boothName] = nil TriggerClientEvent('dj:client:stopMusic', -1, boothName) end TriggerClientEvent('QBCore:Notify', source, 'Alle DJ-Booths wurden gestoppt!', 'success') print('[DJ System] Admin ' .. GetPlayerName(source) .. ' hat alle DJ-Booths gestoppt') end) QBCore.Commands.Add('djstatus', 'Zeige Status aller DJ-Booths (Admin)', {}, false, function(source, args) local Player = QBCore.Functions.GetPlayer(source) if Player.PlayerData.job.name ~= 'admin' and Player.PlayerData.job.name ~= 'police' then TriggerClientEvent('QBCore:Notify', source, 'Du hast keine Berechtigung für diesen Befehl!', 'error') return end local status = {} for boothName, boothData in pairs(activeDJBooths) do table.insert(status, { booth = boothName, song = boothData.title, dj = boothData.dj.name, volume = boothData.volume }) end if #status == 0 then TriggerClientEvent('QBCore:Notify', source, 'Keine aktiven DJ-Booths!', 'info') else for _, booth in ipairs(status) do TriggerClientEvent('QBCore:Notify', source, booth.booth .. ': ' .. booth.song .. ' (DJ: ' .. booth.dj .. ', Vol: ' .. booth.volume .. '%)', 'info') end end end) -- Exports exports('GetActiveDJs', function() return activeDJBooths end) exports('StopBooth', function(boothName) if activeDJBooths[boothName] then -- Aktualisiere Session-Ende in der Datenbank local djData = activeDJBooths[boothName] if djData and djData.dj then MySQL.update('UPDATE dj_session_history SET session_end = NOW() WHERE dj_citizenid = ? AND booth_name = ? AND session_end IS NULL', { djData.dj.id, boothName }) end activeDJBooths[boothName] = nil TriggerClientEvent('dj:client:stopMusic', -1, boothName) return true end return false end) -- Initialisierung print('[DJ System] Server gestartet')