1
0
Fork 0
forked from Simnation/Main
This commit is contained in:
Nordi98 2025-08-03 17:36:16 +02:00
parent a3ab70ec94
commit f6b296d053
4 changed files with 1055 additions and 936 deletions

File diff suppressed because it is too large Load diff

View file

@ -1,72 +1,70 @@
Config = {} Config = {}
-- Allgemeine Einstellungen -- Allgemeine Einstellungen
Config.UseJobRestriction = true Config.UseJobRestriction = true -- true = nur bestimmte Jobs können das Script nutzen
Config.AllowedJobs = { Config.AllowedJobs = {
'dj', 'dj',
'nightclub', 'nightclub',
'admin' 'admin'
} }
Config.OpenMenuKey = 'F7' Config.OpenMenuKey = 'F7' -- Taste zum Öffnen des Menüs
Config.MaxVolume = 100 Config.MaxVolume = 100
Config.DefaultVolume = 50 Config.DefaultVolume = 50
-- YouTube API Einstellungen
Config.YouTubeAPI = {
enabled = true,
-- Du kannst eine kostenlose API von https://rapidapi.com/ytjar/api/youtube-mp36 bekommen
-- Oder eine andere YouTube to MP3 API verwenden
apiUrl = "https://youtube-mp36.p.rapidapi.com/dl",
apiKey = "DEIN_API_KEY_HIER", -- Ersetze mit deinem API Key
headers = {
["X-RapidAPI-Key"] = "DEIN_API_KEY_HIER",
["X-RapidAPI-Host"] = "youtube-mp36.p.rapidapi.com"
}
}
-- Alternative: Lokaler YouTube-DL Server (empfohlen für bessere Performance)
Config.LocalYouTubeConverter = {
enabled = false, -- Setze auf true wenn du einen lokalen Converter verwendest
serverUrl = "http://localhost:3000/convert" -- URL zu deinem lokalen Converter
}
-- DJ Booth Locations -- DJ Booth Locations
Config.DJBooths = { Config.DJBooths = {
{ ["Vanilla Unicorn"] = {
name = "Vanilla Unicorn",
coords = vector3(120.13, -1281.72, 29.48), coords = vector3(120.13, -1281.72, 29.48),
range = 50.0, range = 30.0,
maxRange = 100.0 maxRange = 50.0
}, },
{ ["Bahama Mamas"] = {
name = "Bahama Mamas",
coords = vector3(-1387.08, -618.52, 30.82), coords = vector3(-1387.08, -618.52, 30.82),
range = 50.0, range = 25.0,
maxRange = 100.0 maxRange = 45.0
}, },
{ ["Diamond Casino"] = {
name = "Diamond Casino",
coords = vector3(1549.78, 252.44, -46.01), coords = vector3(1549.78, 252.44, -46.01),
range = 60.0, range = 35.0,
maxRange = 120.0 maxRange = 60.0
},
["Galaxy Nightclub"] = {
coords = vector3(-1605.78, -3012.47, -77.79),
range = 40.0,
maxRange = 60.0
},
["Tequi-la-la"] = {
coords = vector3(-564.14, 275.35, 83.02),
range = 25.0,
maxRange = 40.0
} }
} }
-- Standard Playlists mit YouTube Links -- Standard Playlists
Config.DefaultPlaylists = { Config.DefaultPlaylists = {
{ {
name = "Party Hits", name = "Party Hits",
songs = { songs = {
{title = "Daft Punk - One More Time", url = "https://www.youtube.com/watch?v=FGBhQbmPwH8"}, {title = "Daft Punk - One More Time", url = "https://www.youtube.com/watch?v=FGBhQbmPwH8"},
{title = "Calvin Harris - Feel So Close", url = "https://www.youtube.com/watch?v=dGghkjpNCQ8"} {title = "Avicii - Levels", url = "https://www.youtube.com/watch?v=_ovdm2yX4MA"}
} }
}, },
{ {
name = "Chill Music", name = "Chill Vibes",
songs = { songs = {
{title = "Kygo - Firestone", url = "https://www.youtube.com/watch?v=9Sc-ir2UwGU"}, {title = "Kygo - Firestone", url = "https://www.youtube.com/watch?v=9Sc-ir2UwGU"},
{title = "Avicii - Levels", url = "https://www.youtube.com/watch?v=_ovdm2yX4MA"} {title = "Kygo - Stole The Show", url = "https://www.youtube.com/watch?v=BgfcToAjfdc"}
} }
} }
} }
-- Distanz-basierte Lautstärke
Config.DistanceVolume = {
enabled = true,
updateInterval = 1000, -- Wie oft die Lautstärke aktualisiert wird (ms)
fadeTime = 1000 -- Wie lange der Fade-Effekt dauert (ms)
}
-- Debug-Modus
Config.Debug = false

View file

@ -163,12 +163,6 @@ window.addEventListener('message', function(event) {
case 'setVolume': case 'setVolume':
setVolume(data.volume); setVolume(data.volume);
break; break;
case 'pauseMusic':
pauseMusic();
break;
case 'resumeMusic':
resumeMusic();
break;
} }
}); });
@ -352,7 +346,7 @@ function extractYouTubeVideoId(url) {
// FiveM benachrichtigen // FiveM benachrichtigen
function notifyFiveM(event, data) { function notifyFiveM(event, data) {
fetch(`https://${GetParentResourceName()}/` + event, { fetch(`https://${GetParentResourceName()}/${event}`, {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'

View file

@ -1,74 +1,7 @@
local QBCore = exports['qb-core']:GetCoreObject() local QBCore = exports['qb-core']:GetCoreObject()
-- YouTube URL Converter -- Aktive DJ-Booths
local function ConvertYouTubeUrl(url, callback) local activeDJBooths = {}
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 -- Database Setup
CreateThread(function() CreateThread(function()
@ -77,7 +10,10 @@ CreateThread(function()
id INT AUTO_INCREMENT PRIMARY KEY, id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL,
owner VARCHAR(50) NOT NULL, owner VARCHAR(50) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP 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
) )
]]) ]])
@ -86,9 +22,15 @@ CreateThread(function()
id INT AUTO_INCREMENT PRIMARY KEY, id INT AUTO_INCREMENT PRIMARY KEY,
playlist_id INT NOT NULL, playlist_id INT NOT NULL,
title VARCHAR(255) NOT NULL, title VARCHAR(255) NOT NULL,
artist VARCHAR(255) DEFAULT NULL,
url TEXT NOT NULL, url TEXT NOT NULL,
converted_url TEXT NULL, converted_url TEXT DEFAULT NULL,
duration INT DEFAULT NULL,
position INT DEFAULT 0, 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 FOREIGN KEY (playlist_id) REFERENCES dj_playlists(id) ON DELETE CASCADE
) )
]]) ]])
@ -96,181 +38,46 @@ CreateThread(function()
MySQL.query([[ MySQL.query([[
CREATE TABLE IF NOT EXISTS dj_url_cache ( CREATE TABLE IF NOT EXISTS dj_url_cache (
id INT AUTO_INCREMENT PRIMARY KEY, id INT AUTO_INCREMENT PRIMARY KEY,
original_url VARCHAR(500) NOT NULL UNIQUE, original_url VARCHAR(500) NOT NULL,
converted_url TEXT 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, expires_at TIMESTAMP NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP 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) end)
-- Events -- YouTube URL Funktionen
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) local function extractYouTubeVideoId(url)
-- Verschiedene YouTube URL Formate unterstützen -- YouTube URL Patterns
local patterns = { local patterns = {
"youtube%.com/watch%?v=([^&]+)", "youtube%.com/watch%?v=([%w%-_]+)",
"youtu%.be/([^?]+)", "youtube%.com/watch%?.*&v=([%w%-_]+)",
"youtube%.com/embed/([^?]+)", "youtu%.be/([%w%-_]+)",
"youtube%.com/v/([^?]+)" "youtube%.com/embed/([%w%-_]+)"
} }
for _, pattern in ipairs(patterns) do for _, pattern in ipairs(patterns) do
@ -293,128 +100,476 @@ local function cleanYouTubeUrl(url)
return "https://www.youtube.com/watch?v=" .. videoId return "https://www.youtube.com/watch?v=" .. videoId
end end
-- YouTube zu MP3 Konvertierung (mit yt-dlp oder youtube-dl) -- Events
local function convertYouTubeUrl(url, callback) RegisterServerEvent('dj:server:playMusic')
local videoId = extractYouTubeVideoId(url) AddEventHandler('dj:server:playMusic', function(data)
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
-- Aktualisierte PlayMusic Funktion
RegisterServerEvent('dj:playMusic')
AddEventHandler('dj:playMusic', function(title, url, volume)
local src = source local src = source
local Player = QBCore.Functions.GetPlayer(src) local Player = QBCore.Functions.GetPlayer(src)
if not Player then return end if not Player then return end
-- Bereinige YouTube URL (entferne Playlist-Parameter) -- Speichere den aktuellen DJ-Status
local cleanUrl = url activeDJBooths[data.booth.name] = {
if string.find(url, "youtube%.com") or string.find(url, "youtu%.be") then url = data.url,
-- Entferne &list= und andere Parameter title = data.title,
cleanUrl = string.gsub(url, "&list=.-$", "") volume = data.volume,
cleanUrl = string.gsub(cleanUrl, "&start_radio=.-$", "") range = data.range,
cleanUrl = string.gsub(cleanUrl, "&index=.-$", "") dj = {
id = Player.PlayerData.citizenid,
print('[DJ System] Bereinigte YouTube URL: ' .. cleanUrl) name = Player.PlayerData.charinfo.firstname .. ' ' .. Player.PlayerData.charinfo.lastname
end },
coords = data.booth.coords
}
print('[DJ System] Streaming: ' .. title .. ' | URL: ' .. cleanUrl) -- Sende an alle Spieler
TriggerClientEvent('dj:client:playMusic', -1, data)
-- Sende direkt an alle Clients zum Streamen -- Log für Admin
TriggerClientEvent('dj:playMusicClient', -1, title, cleanUrl, volume) print(('[DJ System] %s spielt Musik ab: %s | Booth: %s'):format(
GetPlayerName(src),
data.title,
data.booth.name
))
-- Speichere in Datenbank -- Speichere in Datenbank
local songType = 'direct' MySQL.insert('INSERT INTO dj_session_history (dj_citizenid, dj_name, booth_name, song_title, song_url, song_type, volume, session_start) VALUES (?, ?, ?, ?, ?, ?, ?, NOW())', {
if string.find(cleanUrl, "youtube") then
songType = 'youtube'
end
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.citizenid,
Player.PlayerData.charinfo.firstname .. ' ' .. Player.PlayerData.charinfo.lastname, Player.PlayerData.charinfo.firstname .. ' ' .. Player.PlayerData.charinfo.lastname,
'DJ Booth', data.booth.name,
title, data.title,
cleanUrl, data.url,
songType, string.match(data.url, "youtube") and 'youtube' or 'direct',
volume data.volume
}) })
TriggerClientEvent('QBCore:Notify', src, 'Streaming: ' .. title, 'success')
end) end)
-- Audio Error Handler RegisterServerEvent('dj:server:stopMusic')
RegisterNUICallback('audioError', function(data, cb) AddEventHandler('dj:server:stopMusic', function(boothName)
local src = source local src = source
print('[DJ System] Audio Error: ' .. (data.error or 'Unknown'))
TriggerClientEvent('QBCore:Notify', src, 'Audio Fehler: ' .. (data.error or 'Unbekannt'), 'error') -- Entferne DJ-Status
cb('ok') 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) end)
-- Song Ended Handler RegisterServerEvent('dj:server:setVolume')
RegisterNUICallback('songEnded', function(data, cb) AddEventHandler('dj:server:setVolume', function(data)
print('[DJ System] Song ended') local src = source
-- Hier könntest du Playlist-Logik hinzufügen
cb('ok') -- 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) 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')