local QBCore = exports['qb-core']:GetCoreObject() -- YouTube URL Converter local function ConvertYouTubeUrl(url, callback) if not string.match(url, "youtube%.com") and not string.match(url, "youtu%.be") then callback(url) -- Nicht YouTube, direkt zurückgeben return end -- Extrahiere Video ID local videoId = ExtractYouTubeVideoId(url) if not videoId then callback(nil) return end if Config.LocalYouTubeConverter.enabled then -- Verwende lokalen Converter PerformHttpRequest(Config.LocalYouTubeConverter.serverUrl, function(errorCode, resultData, resultHeaders) if errorCode == 200 then local data = json.decode(resultData) if data and data.url then callback(data.url) else callback(nil) end else callback(nil) end end, 'POST', json.encode({videoId = videoId}), {['Content-Type'] = 'application/json'}) elseif Config.YouTubeAPI.enabled then -- Verwende externe API local apiUrl = Config.YouTubeAPI.apiUrl .. "?id=" .. videoId PerformHttpRequest(apiUrl, function(errorCode, resultData, resultHeaders) if errorCode == 200 then local data = json.decode(resultData) if data and data.link then callback(data.link) else callback(nil) end else print("YouTube API Error: " .. errorCode) callback(nil) end end, 'GET', '', Config.YouTubeAPI.headers) else callback(nil) end end 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 -- 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, created_at TIMESTAMP DEFAULT 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, url TEXT NOT NULL, converted_url TEXT NULL, position INT DEFAULT 0, 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 UNIQUE, converted_url TEXT NOT NULL, expires_at TIMESTAMP NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ]]) end) -- Events RegisterNetEvent('dj:server:playMusic', function(data) local src = source -- Prüfe Cache zuerst MySQL.query('SELECT converted_url FROM dj_url_cache WHERE original_url = ? AND expires_at > NOW()', {data.url}, function(cached) if cached[1] then -- Verwende gecachte URL local musicData = { title = data.title, url = cached[1].converted_url, volume = data.volume, booth = data.booth, range = data.range } TriggerClientEvent('dj:client:playMusic', -1, musicData) print(('[DJ System] %s spielt Musik ab (cached): %s'):format(GetPlayerName(src), data.title)) else -- Konvertiere URL ConvertYouTubeUrl(data.url, function(convertedUrl) if convertedUrl then -- Cache die URL für 1 Stunde MySQL.insert('INSERT INTO dj_url_cache (original_url, converted_url, expires_at) VALUES (?, ?, DATE_ADD(NOW(), INTERVAL 1 HOUR)) ON DUPLICATE KEY UPDATE converted_url = VALUES(converted_url), expires_at = VALUES(expires_at)', { data.url, convertedUrl }) local musicData = { title = data.title, url = convertedUrl, volume = data.volume, booth = data.booth, range = data.range } TriggerClientEvent('dj:client:playMusic', -1, musicData) print(('[DJ System] %s spielt Musik ab (converted): %s'):format(GetPlayerName(src), data.title)) else TriggerClientEvent('dj:client:notify', src, 'Fehler beim Konvertieren der YouTube URL!', 'error') print(('[DJ System] Fehler beim Konvertieren der URL: %s'):format(data.url)) end end) end end) end) RegisterNetEvent('dj:server:stopMusic', function(booth) local src = source TriggerClientEvent('dj:client:stopMusic', -1) print(('[DJ System] %s hat die Musik gestoppt'):format(GetPlayerName(src))) end) RegisterNetEvent('dj:server:setVolume', function(data) local src = source TriggerClientEvent('dj:client:setVolume', -1, data.volume) print(('[DJ System] %s hat die Lautstärke auf %d%% gesetzt'):format(GetPlayerName(src), data.volume)) end) RegisterNetEvent('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 = ?', {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, 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) RegisterNetEvent('dj:server:createPlaylist', function(name) local src = source local Player = QBCore.Functions.GetPlayer(src) if not Player then return end MySQL.insert('INSERT INTO dj_playlists (name, owner) VALUES (?, ?)', { name, Player.PlayerData.citizenid }, function(id) if id then TriggerClientEvent('dj:client:notify', src, 'Playlist "' .. name .. '" wurde erstellt!', 'success') TriggerEvent('dj:server:getPlaylists') else TriggerClientEvent('dj:client:notify', src, 'Fehler beim Erstellen der Playlist!', 'error') end end) end) RegisterNetEvent('dj:server:addSongToPlaylist', function(playlistId, song) local src = source local Player = QBCore.Functions.GetPlayer(src) if not Player then return end MySQL.query('SELECT * FROM dj_playlists WHERE id = ? AND owner = ?', { playlistId, Player.PlayerData.citizenid }, function(result) if result[1] then MySQL.insert('INSERT INTO dj_playlist_songs (playlist_id, title, url) VALUES (?, ?, ?)', { playlistId, song.title, song.url }, function(id) if id then TriggerClientEvent('dj:client:notify', src, 'Song wurde zur Playlist hinzugefügt!', 'success') TriggerEvent('dj:server:getPlaylists') else TriggerClientEvent('dj:client:notify', src, 'Fehler beim Hinzufügen des Songs!', 'error') end end) else TriggerClientEvent('dj:client:notify', src, 'Du hast keine Berechtigung für diese Playlist!', 'error') end end) end) RegisterNetEvent('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('dj:client:notify', src, 'Playlist wurde gelöscht!', 'success') TriggerEvent('dj:server:getPlaylists') else TriggerClientEvent('dj:client:notify', src, 'Fehler beim Löschen der Playlist!', 'error') 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) -- Füge diese Funktionen zu deiner server/main.lua hinzu: local function extractYouTubeVideoId(url) -- Verschiedene YouTube URL Formate unterstützen local patterns = { "youtube%.com/watch%?v=([^&]+)", "youtu%.be/([^?]+)", "youtube%.com/embed/([^?]+)", "youtube%.com/v/([^?]+)" } 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 -- YouTube zu MP3 Konvertierung (mit yt-dlp oder youtube-dl) local function convertYouTubeUrl(url, callback) local videoId = extractYouTubeVideoId(url) if not videoId then callback(nil, "Ungültige YouTube URL") return end -- Prüfe Cache zuerst MySQL.Async.fetchScalar('SELECT converted_url FROM dj_url_cache WHERE original_url = ? AND expires_at > NOW()', {url}, function(cachedUrl) if cachedUrl then print('[DJ System] URL aus Cache geladen: ' .. videoId) callback(cachedUrl, nil) -- Update hit count MySQL.Async.execute('UPDATE dj_url_cache SET hit_count = hit_count + 1, last_accessed = NOW() WHERE original_url = ?', {url}) return end -- Konvertiere mit yt-dlp (externe Lösung) convertWithYtDlp(url, videoId, callback) end) end -- Konvertierung mit yt-dlp (benötigt yt-dlp Installation) local function convertWithYtDlp(originalUrl, videoId, callback) local cleanUrl = "https://www.youtube.com/watch?v=" .. videoId -- yt-dlp Befehl ausführen local command = string.format('yt-dlp --get-url --format "bestaudio[ext=m4a]/best[ext=mp4]/best" "%s"', cleanUrl) -- Führe Befehl aus (das ist ein vereinfachtes Beispiel) -- In der Praxis würdest du einen HTTP Request an einen externen Service machen -- Für jetzt: Verwende eine alternative Lösung useAlternativeConversion(originalUrl, videoId, callback) end -- Alternative: Verwende einen Online-Service local function useAlternativeConversion(originalUrl, videoId, callback) -- Option 1: Verwende YouTube Embed URL (funktioniert manchmal) local embedUrl = "https://www.youtube.com/embed/" .. videoId .. "?autoplay=1" -- Option 2: Verwende einen kostenlosen Konvertierungsservice -- ACHTUNG: Diese Services können instabil sein! local convertedUrl = "https://youtube-mp3-download.com/api/convert/" .. videoId -- Option 3: Verwende die originale URL und lass den Client damit umgehen local fallbackUrl = "https://www.youtube.com/watch?v=" .. videoId -- Speichere im Cache (24 Stunden gültig) local expiresAt = os.date('%Y-%m-%d %H:%M:%S', os.time() + 86400) MySQL.Async.execute('INSERT INTO dj_url_cache (original_url, converted_url, video_id, expires_at) VALUES (?, ?, ?, ?) ON DUPLICATE KEY UPDATE converted_url = VALUES(converted_url), expires_at = VALUES(expires_at)', { originalUrl, fallbackUrl, -- Verwende fallback URL videoId, expiresAt }, function(affectedRows) if affectedRows > 0 then print('[DJ System] URL in Cache gespeichert: ' .. videoId) end callback(fallbackUrl, nil) end) end -- Aktualisiere die PlayMusic Funktion RegisterServerEvent('dj:playMusic') AddEventHandler('dj:playMusic', function(title, url, volume) local src = source local Player = QBCore.Functions.GetPlayer(src) if not Player then return end print('[DJ System] Spiele Musik ab: ' .. title .. ' | URL: ' .. url) -- Prüfe ob es eine YouTube URL ist if string.match(url, "youtube%.com") or string.match(url, "youtu%.be") then convertYouTubeUrl(url, function(convertedUrl, error) if error then TriggerClientEvent('QBCore:Notify', src, 'Fehler beim Konvertieren: ' .. error, 'error') return end if convertedUrl then -- Sende konvertierte URL an alle Clients TriggerClientEvent('dj:playMusicClient', -1, title, convertedUrl, volume) -- Speichere in Session History MySQL.Async.execute('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, 'Unknown', -- Du kannst hier den Booth-Namen hinzufügen title, url, 'youtube', volume }) else TriggerClientEvent('QBCore:Notify', src, 'Konvertierung fehlgeschlagen', 'error') end end) else -- Direkte Audio URL TriggerClientEvent('dj:playMusicClient', -1, title, url, volume) -- Speichere in Session History MySQL.Async.execute('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, 'Unknown', title, url, 'direct', volume }) end end)