forked from Simnation/Main
ed
This commit is contained in:
parent
a3ab70ec94
commit
f6b296d053
4 changed files with 1055 additions and 936 deletions
File diff suppressed because it is too large
Load diff
|
@ -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
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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')
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue