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 = {}
|
||||
|
||||
-- Allgemeine Einstellungen
|
||||
Config.UseJobRestriction = true
|
||||
Config.UseJobRestriction = true -- true = nur bestimmte Jobs können das Script nutzen
|
||||
Config.AllowedJobs = {
|
||||
'dj',
|
||||
'nightclub',
|
||||
'admin'
|
||||
}
|
||||
|
||||
Config.OpenMenuKey = 'F7'
|
||||
Config.OpenMenuKey = 'F7' -- Taste zum Öffnen des Menüs
|
||||
Config.MaxVolume = 100
|
||||
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
|
||||
Config.DJBooths = {
|
||||
{
|
||||
name = "Vanilla Unicorn",
|
||||
["Vanilla Unicorn"] = {
|
||||
coords = vector3(120.13, -1281.72, 29.48),
|
||||
range = 50.0,
|
||||
maxRange = 100.0
|
||||
range = 30.0,
|
||||
maxRange = 50.0
|
||||
},
|
||||
{
|
||||
name = "Bahama Mamas",
|
||||
["Bahama Mamas"] = {
|
||||
coords = vector3(-1387.08, -618.52, 30.82),
|
||||
range = 50.0,
|
||||
maxRange = 100.0
|
||||
range = 25.0,
|
||||
maxRange = 45.0
|
||||
},
|
||||
{
|
||||
name = "Diamond Casino",
|
||||
["Diamond Casino"] = {
|
||||
coords = vector3(1549.78, 252.44, -46.01),
|
||||
range = 60.0,
|
||||
maxRange = 120.0
|
||||
range = 35.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 = {
|
||||
{
|
||||
name = "Party Hits",
|
||||
songs = {
|
||||
{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 = {
|
||||
{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':
|
||||
setVolume(data.volume);
|
||||
break;
|
||||
case 'pauseMusic':
|
||||
pauseMusic();
|
||||
break;
|
||||
case 'resumeMusic':
|
||||
resumeMusic();
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -352,7 +346,7 @@ function extractYouTubeVideoId(url) {
|
|||
|
||||
// FiveM benachrichtigen
|
||||
function notifyFiveM(event, data) {
|
||||
fetch(`https://${GetParentResourceName()}/` + event, {
|
||||
fetch(`https://${GetParentResourceName()}/${event}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
|
|
|
@ -1,74 +1,7 @@
|
|||
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
|
||||
-- Aktive DJ-Booths
|
||||
local activeDJBooths = {}
|
||||
|
||||
-- Database Setup
|
||||
CreateThread(function()
|
||||
|
@ -77,7 +10,10 @@ CreateThread(function()
|
|||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(255) 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,
|
||||
playlist_id INT NOT NULL,
|
||||
title VARCHAR(255) NOT NULL,
|
||||
artist VARCHAR(255) DEFAULT NULL,
|
||||
url TEXT NOT NULL,
|
||||
converted_url TEXT 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
|
||||
)
|
||||
]])
|
||||
|
@ -96,181 +38,46 @@ CreateThread(function()
|
|||
MySQL.query([[
|
||||
CREATE TABLE IF NOT EXISTS dj_url_cache (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
original_url VARCHAR(500) NOT NULL UNIQUE,
|
||||
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,
|
||||
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)
|
||||
|
||||
-- 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:
|
||||
|
||||
-- YouTube URL Funktionen
|
||||
local function extractYouTubeVideoId(url)
|
||||
-- Verschiedene YouTube URL Formate unterstützen
|
||||
-- YouTube URL Patterns
|
||||
local patterns = {
|
||||
"youtube%.com/watch%?v=([^&]+)",
|
||||
"youtu%.be/([^?]+)",
|
||||
"youtube%.com/embed/([^?]+)",
|
||||
"youtube%.com/v/([^?]+)"
|
||||
"youtube%.com/watch%?v=([%w%-_]+)",
|
||||
"youtube%.com/watch%?.*&v=([%w%-_]+)",
|
||||
"youtu%.be/([%w%-_]+)",
|
||||
"youtube%.com/embed/([%w%-_]+)"
|
||||
}
|
||||
|
||||
for _, pattern in ipairs(patterns) do
|
||||
|
@ -293,128 +100,476 @@ local function cleanYouTubeUrl(url)
|
|||
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
|
||||
|
||||
-- Aktualisierte PlayMusic Funktion
|
||||
RegisterServerEvent('dj:playMusic')
|
||||
AddEventHandler('dj:playMusic', function(title, url, volume)
|
||||
-- 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
|
||||
|
||||
-- Bereinige YouTube URL (entferne Playlist-Parameter)
|
||||
local cleanUrl = url
|
||||
if string.find(url, "youtube%.com") or string.find(url, "youtu%.be") then
|
||||
-- Entferne &list= und andere Parameter
|
||||
cleanUrl = string.gsub(url, "&list=.-$", "")
|
||||
cleanUrl = string.gsub(cleanUrl, "&start_radio=.-$", "")
|
||||
cleanUrl = string.gsub(cleanUrl, "&index=.-$", "")
|
||||
-- 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
|
||||
}
|
||||
|
||||
print('[DJ System] Bereinigte YouTube URL: ' .. cleanUrl)
|
||||
end
|
||||
-- Sende an alle Spieler
|
||||
TriggerClientEvent('dj:client:playMusic', -1, data)
|
||||
|
||||
print('[DJ System] Streaming: ' .. title .. ' | URL: ' .. cleanUrl)
|
||||
|
||||
-- Sende direkt an alle Clients zum Streamen
|
||||
TriggerClientEvent('dj:playMusicClient', -1, title, cleanUrl, volume)
|
||||
-- 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
|
||||
local songType = 'direct'
|
||||
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())', {
|
||||
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,
|
||||
'DJ Booth',
|
||||
title,
|
||||
cleanUrl,
|
||||
songType,
|
||||
volume
|
||||
data.booth.name,
|
||||
data.title,
|
||||
data.url,
|
||||
string.match(data.url, "youtube") and 'youtube' or 'direct',
|
||||
data.volume
|
||||
})
|
||||
|
||||
TriggerClientEvent('QBCore:Notify', src, 'Streaming: ' .. title, 'success')
|
||||
end)
|
||||
|
||||
-- Audio Error Handler
|
||||
RegisterNUICallback('audioError', function(data, cb)
|
||||
RegisterServerEvent('dj:server:stopMusic')
|
||||
AddEventHandler('dj:server:stopMusic', function(boothName)
|
||||
local src = source
|
||||
print('[DJ System] Audio Error: ' .. (data.error or 'Unknown'))
|
||||
|
||||
TriggerClientEvent('QBCore:Notify', src, 'Audio Fehler: ' .. (data.error or 'Unbekannt'), 'error')
|
||||
cb('ok')
|
||||
-- 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)
|
||||
|
||||
-- Song Ended Handler
|
||||
RegisterNUICallback('songEnded', function(data, cb)
|
||||
print('[DJ System] Song ended')
|
||||
-- Hier könntest du Playlist-Logik hinzufügen
|
||||
cb('ok')
|
||||
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')
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue