1
0
Fork 0
forked from Simnation/Main
This commit is contained in:
Nordi98 2025-07-12 18:56:43 +02:00
parent 4c44afc37c
commit 2e129ccb92
26 changed files with 3020 additions and 2 deletions

View file

@ -1311,8 +1311,18 @@ CodeStudio.Products = {
itemPrice = 50,
itemInfo = "",
},
['Bodycam'] = {
itemName = "Bodycam",
itemStock = 150,
itemPrice = 50,
itemInfo = "",
},
['Dashcam'] = {
itemName = "Dashcam",
itemStock = 150,
itemPrice = 50,
itemInfo = "",
},
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

View file

@ -10401,5 +10401,28 @@ itemsData = {
image = 'kq_winch.png',
name = 'kq_winch',
},
bodycam = {
shouldClose = true,
type = 'item',
description = '',
weight = 500,
label = 'Bodycam',
unique = true,
useable = true,
image = 'bodycam.png',
name = 'bodycam',
},
dashcam = {
shouldClose = true,
type = 'item',
description = '',
weight = 500,
label = 'Dashcam',
unique = true,
useable = true,
image = 'dashcam.png',
name = 'dashcam',
},
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,86 @@
CreateThread(function()
if Config.Dependency.UseTarget == 'qb' then
for k,v in ipairs(Config.WatchLoc) do
exports['qb-target']:AddCircleZone("spycam_watch"..k,v.coords,v.rad, {
name = "spycam_watch"..k,
useZ=true,
debugPoly = v.debug,
}, {
options = {
{
type = "client",
icon = "fas fa-sign-in-alt",
label = 'Open Camera Portal',
action = function(entity)
TriggerEvent('spy-bodycam:openActiveMenu',k)
end,
canInteract = function(entity)
return targetAuth(k,PlayerJob.name)
end,
},
{
type = "client",
icon = "fas fa-list",
label = 'Records Database',
action = function(entity)
TriggerServerEvent('spy-bodycam:server:showrecordingUI')
end,
canInteract = function(entity)
return targetAuth(k,PlayerJob.name)
end,
},
},
distance = 2.5
})
end
else
for k,v in ipairs(Config.WatchLoc) do
exports.ox_target:addSphereZone(
{
coords = v.coords,
radius = v.rad,
debug = v.debug,
drawSprite = false,
options = {
{
name = "spycam_watch"..k,
label = 'Open Camera Portal',
icon = "fas fa-sign-in-alt",
distance = 2.5,
onSelect = function(data)
TriggerEvent('spy-bodycam:openActiveMenu',k)
end,
canInteract = function(entity, distance, coords, name, bone)
return targetAuth(k,PlayerJob.name)
end,
},
{
name = "spycam_watch"..k,
label = 'Records Database',
icon = "fas fa-list",
distance = 2.5,
onSelect = function(data)
TriggerServerEvent('spy-bodycam:server:showrecordingUI')
end,
canInteract = function(entity, distance, coords, name, bone)
return targetAuth(k,PlayerJob.name)
end,
},
}
}
)
end
end
end)
AddEventHandler('onResourceStop', function(resourceName)
if GetCurrentResourceName() == resourceName then
if Config.Dependency.UseTarget == 'qb' then
for k,v in ipairs(Config.WatchLoc) do
exports['qb-target']:RemoveZone("spycam_watch"..k)
end
end
end
end)

View file

@ -0,0 +1,102 @@
-- ███████╗██████╗ ██╗ ██╗ ██████╗ ██████╗ ██████╗ ██╗ ██╗ ██████╗ █████╗ ███╗ ███╗
-- ██╔════╝██╔══██╗╚██╗ ██╔╝ ██╔══██╗██╔═══██╗██╔══██╗╚██╗ ██╔╝██╔════╝██╔══██╗████╗ ████║
-- ███████╗██████╔╝ ╚████╔╝ ██████╔╝██║ ██║██║ ██║ ╚████╔╝ ██║ ███████║██╔████╔██║
-- ╚════██║██╔═══╝ ╚██╔╝ ██╔══██╗██║ ██║██║ ██║ ╚██╔╝ ██║ ██╔══██║██║╚██╔╝██║
-- ███████║██║ ██║ ██████╔╝╚██████╔╝██████╔╝ ██║ ╚██████╗██║ ██║██║ ╚═╝ ██║
-- ╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═════╝╚═╝ ╚═╝╚═╝ ╚═╝
Config = Config or {}
Config.Framework = 'qb' -- qb | esx
if Config.Framework == 'qb' then -- Dont touch this part
QBCore = exports["qb-core"]:GetCoreObject()
elseif Config.Framework == 'esx' then
ESX = exports.es_extended:getSharedObject()
end
Config.Dependency = {
UseTarget = 'qb', -- qb | ox
UseInventory = 'qb', -- qb | ox | esx
UseProgress = 'qb', -- qb | ox | esx
UseMenu = 'qb', -- qb | ox | esx
UseNotify = 'qb', -- qb | ox | esx
UseAppearance = 'illenium', -- qb | illenium | false
}
Config.ExitCamKey = 'BACK'
Config.CameraEffect = {
bodycam = 'Island_CCTV_ChannelFuzz',
dashcam = 'TinyRacerMoBlur',
}
Config.ForceViewCam = false -- Forces cam view to first person when recording.
Config.RecordTime = 30 --Recording Time | 30 = 30 seconds | [*note: Dont increase the time too much or it may not upload to any service you are using for example - discord.]
Config.PropLoc = { -- Change prop position according to ur clothing pack.
male = {
bone = 24818,
pos = vector3(0.16683200771922, 0.11320925137666, 0.11986595654326),
rot = vector3(-14.502323044318, 82.191095946679, -164.22066869048),
},
female = {
bone = 24818,
pos = vector3(0.16683200771922, 0.11320925137666, 0.11986595654326),
rot = vector3(-14.502323044318, 82.191095946679, -164.22066869048),
},
}
Config.AllowedJobs = { -- Only these jobs can use bodycam/dashcam item.
'police',
'ambulance',
}
Config.AllowedClass = {18} -- Vehicle classes allowed to use the dashcam feature.
Config.WatchLoc = {
[1] = {
coords = vector3(440.149445, -979.437378, 30.453491),
rad = 1.5,
debug = false,
jobCam = {'police','ambulance'}, -- jobs mentioned here are shown in the list | false = able to view all the bodycams
carCam = { -- false = able to view all the dashcams
job = {'police'}, -- Jobs that activate dashcams shown in the list | false excludes.
class = {18} -- Dashcam activated on these vehicleclass shown in the list | false excludes.
},
targetAuth = {'police'}, -- jobs mentioned here can use this location from target | false = everyone can access this location
}, -- You can add more locations
}
Config.DebugCamera = false -- Make it true if you want to get new camera offset for some vehicle.
Config.VehCamOffset = {
[`police2`] = {0.000000, 0.330000, 0.530000},
-- [`18chgr2`] = {0.000000, 0.510000, 0.630000}, -- Example vehicle. The script comes with its own offset finder just set DebugCamera to true and get the offset.
-- [`vehiclespawncode`] = {0.000000, 0.510000, 0.630000},
}
-- Vehicle Classes:
-- 0: Compacts
-- 1: Sedans
-- 2: SUVs
-- 3: Coupes
-- 4: Muscle
-- 5: Sports Classics
-- 6: Sports
-- 7: Super
-- 8: Motorcycles
-- 9: Off-road
-- 10: Industrial
-- 11: Utility
-- 12: Vans
-- 13: Cycles
-- 14: Boats
-- 15: Helicopters
-- 16: Planes
-- 17: Service
-- 18: Emergency
-- 19: Military
-- 20: Commercial
-- 21: Trains
-- 22: Open Wheel

View file

@ -0,0 +1,29 @@
fx_version 'cerulean'
game "gta5"
author "Spy-Script"
version '2.5.6'
lua54 'yes'
ui_page 'web/index.html'
shared_script {
"config.lua",
'@ox_lib/init.lua',
}
server_script {
"server/**",
'@oxmysql/lib/MySQL.lua',
}
client_script {
'client/**',
}
files {
'web/**',
"node_modules/fivem-game-view/**/*",
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

View file

@ -0,0 +1,24 @@
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET NAMES utf8 */;
/*!50503 SET NAMES utf8mb4 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
CREATE TABLE IF NOT EXISTS `spy_bodycam` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`job` varchar(255) NOT NULL,
`videolink` longtext NOT NULL,
`street` varchar(255) NOT NULL,
`date` varchar(255) NOT NULL,
`playername` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=42 DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
/*!40103 SET TIME_ZONE=IFNULL(@OLD_TIME_ZONE, 'system') */;
/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */;
/*!40014 SET FOREIGN_KEY_CHECKS=IFNULL(@OLD_FOREIGN_KEY_CHECKS, 1) */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40111 SET SQL_NOTES=IFNULL(@OLD_SQL_NOTES, 1) */;

View file

@ -0,0 +1,373 @@
PlayerOnBodycam = {}
GlobalState.PlayerOnBodycam = PlayerOnBodycam
CarsOnBodycam = {}
GlobalState.CarsOnBodycam = CarsOnBodycam
lib.callback.register('spy-bodycam:servercb:getPedCoords', function(source, targetId)
local targetPed = GetPlayerPed(targetId)
if targetPed == 0 then return false end
local targetCoords = GetEntityCoords(targetPed)
if targetCoords then return targetCoords end
end)
lib.callback.register('spy-bodycam:servercb:getCarCoords', function(source, netId)
local veh = NetworkGetEntityFromNetworkId(netId)
if DoesEntityExist(veh) then
local targetCoords = GetEntityCoords(veh)
if targetCoords then return targetCoords end
else
CarsOnBodycam[netId] = nil
GlobalState.CarsOnBodycam = CarsOnBodycam
return false
end
end)
lib.callback.register('spy-bodycam:servercb:getNamESX', function(source)
local xPlayer = ESX.GetPlayerFromId(source)
local Name = xPlayer.getName()
if Name then return Name end
end)
RegisterNetEvent('spy-bodycam:server:toggleList',function(bool)
local src = source
if bool then
if Config.Framework == 'qb' then
local Player = QBCore.Functions.GetPlayer(src)
local Name = Player.PlayerData.charinfo.firstname .. " " .. Player.PlayerData.charinfo.lastname
local Job = Player.PlayerData.job.label
local Rank = Player.PlayerData.job.grade.name
PlayerOnBodycam[src] = {
name = Name,
jobkey = Player.PlayerData.job.name,
job = Job,
rank = Rank,
}
else
local xPlayer = ESX.GetPlayerFromId(src)
local Name = xPlayer.getName()
local Job = xPlayer.getJob().label
local Rank = xPlayer.getJob().grade_label
PlayerOnBodycam[src] = {
name = Name,
jobkey = xPlayer.getJob().name,
job = Job,
rank = Rank,
}
end
GlobalState.PlayerOnBodycam = PlayerOnBodycam
else
PlayerOnBodycam[src] = nil
GlobalState.PlayerOnBodycam = PlayerOnBodycam
end
SetTimeout(1000, function()
TriggerClientEvent('spy-bodycam:updatelisteffect',-1)
end)
end)
RegisterNetEvent('spy-bodycam:server:toggleListCars',function(bool,entityId,carPlate,carName,carClass)
local src = source
if bool then
local veh = NetworkGetEntityFromNetworkId(entityId)
if DoesEntityExist(veh) then
local jobkey
local Name
if Config.Framework == 'qb' then
local Player = QBCore.Functions.GetPlayer(src)
jobkey = Player.PlayerData.job.name
Name = Player.PlayerData.charinfo.firstname .. " " .. Player.PlayerData.charinfo.lastname
else
local xPlayer = ESX.GetPlayerFromId(src)
jobkey = xPlayer.getJob().name
Name = xPlayer.getName()
end
CarsOnBodycam[entityId] = {
plate = carPlate,
carname = carName,
name = Name,
jobkey = jobkey,
carclass = carClass,
}
GlobalState.CarsOnBodycam = CarsOnBodycam
else
CarsOnBodycam[entityId] = nil
GlobalState.CarsOnBodycam = CarsOnBodycam
end
else
CarsOnBodycam[entityId] = nil
GlobalState.CarsOnBodycam = CarsOnBodycam
end
SetTimeout(1000, function()
TriggerClientEvent('spy-bodycam:updatelisteffectcar',-1)
end)
end)
RegisterNetEvent('spy-bodycam:server:ReqDeleteDecoyPed',function()
if not Config.Dependency.UseAppearance then return end
local src = source
TriggerClientEvent('spy-bodycam:client:deleteDecoyPed',-1,src)
end)
RegisterNetEvent('spy-bodycam:server:ReqDecoyPed', function(cid, pedCoords)
if not Config.Dependency.UseAppearance then return end
local src = source
local result
local function handleDecoyPed(model, skin, pedCoords, src)
local nPlayers = lib.getNearbyPlayers(vector3(pedCoords.x, pedCoords.y, pedCoords.z), 150)
if nPlayers then
for i = 1, #nPlayers do
TriggerClientEvent('spy-bodycam:client:createDecoyPed', nPlayers[i].id, model, skin, pedCoords, src)
end
end
end
if Config.Framework == 'qb' then
result = MySQL.query.await('SELECT * FROM playerskins WHERE citizenid = ? AND active = ?', {cid, 1})
if result[1] ~= nil then
local skinData = json.decode(result[1].skin)
if Config.Dependency.UseAppearance == 'qb' then
handleDecoyPed(tonumber(result[1].model), skinData, pedCoords, src)
elseif Config.Dependency.UseAppearance == 'illenium' then
handleDecoyPed(joaat(skinData.model), skinData, pedCoords, src)
end
end
elseif Config.Framework == 'esx' then
result = MySQL.single.await("SELECT skin FROM users WHERE identifier = ?", {cid})
if result then
local skinData = json.decode(result.skin)
if Config.Dependency.UseAppearance == 'illenium' then
handleDecoyPed(joaat(skinData.model), skinData, pedCoords, src)
end
end
end
end)
Citizen.CreateThread(function()
if Config.Dependency.UseInventory == 'qb' then
QBCore.Functions.CreateUseableItem('bodycam', function(source, item)
TriggerClientEvent('spy-bodycam:bodycamstatus',source)
end)
QBCore.Functions.CreateUseableItem('dashcam', function(source, item)
TriggerClientEvent('spy-bodycam:toggleCarCam',source)
end)
elseif Config.Dependency.UseInventory == 'esx' then
ESX.RegisterUsableItem('bodycam', function(playerId)
TriggerClientEvent('spy-bodycam:bodycamstatus', playerId)
end)
ESX.RegisterUsableItem('dashcam', function(playerId)
TriggerClientEvent('spy-bodycam:toggleCarCam', playerId)
end)
end
end)
RegisterNetEvent('spy-bodycam:server:logVideoDetails', function(videoUrl,streetName)
local src = source
local offName
local offJob
local offRank
local jobKey
local date = os.date('%Y-%m-%d')
if Config.Framework == 'qb' then
local Player = QBCore.Functions.GetPlayer(src)
offName = Player.PlayerData.charinfo.firstname .. " " .. Player.PlayerData.charinfo.lastname
offJob = Player.PlayerData.job.label
offRank = Player.PlayerData.job.grade.name
jobKey = Player.PlayerData.job.name
else
local xPlayer = ESX.GetPlayerFromId(src)
offName = xPlayer.getName()
offJob = xPlayer.getJob().label
offRank = xPlayer.getJob().grade_label
jobKey = xPlayer.getJob().name
end
---SQL UPLOAD
MySQL.Async.execute('INSERT INTO spy_bodycam (job, videolink, street, date, playername) VALUES (@job, @videolink, @street, @date, @playername)', {
['@job'] = jobKey,
['@videolink'] = videoUrl,
['@street'] = streetName,
['@date'] = date,
['@playername'] = offName
}, function(rowsChanged) end)
if Upload.DiscordLogs.Enabled then
local defwebhook
local author
if Upload.JobUploads[jobKey] then
defwebhook = Upload.JobUploads[jobKey].webhook
author = Upload.JobUploads[jobKey].author
else
defwebhook = Upload.DefaultUploads.webhook
author = Upload.DefaultUploads.author
end
local embedData = {
{
title = Upload.DiscordLogs.Title,
color = 16761035,
fields = {
{ name = "Name:", value = offName, inline = false },
{ name = "Job:", value = offJob, inline = false },
{ name = "Job Rank:", value = offRank, inline = false },
},
footer = {
text = "Date: " .. os.date("!%Y-%m-%d %H:%M:%S", os.time()),
icon_url = "https://i.imgur.com/CuSyeZT.png",
},
author = author
}
}
PerformHttpRequest(defwebhook, function() end, 'POST', json.encode({ username = Upload.DiscordLogs.Username, embeds = embedData}), { ['Content-Type'] = 'application/json' })
end
end)
RegisterNetEvent('spy-bodycam:server:deleteVideoDB', function(videoUrl)
local src = source
if not videoUrl or videoUrl == '' then return end
MySQL.Async.execute('DELETE FROM spy_bodycam WHERE videolink = @videolink', {
['@videolink'] = videoUrl
}, function(rowsChanged)
if rowsChanged > 0 then
local jobKey
local isBoss
if Config.Framework == 'qb' then
local Player = QBCore.Functions.GetPlayer(src)
jobKey = Player.PlayerData.job.name
isBoss = Player.PlayerData.job.isboss
else
local xPlayer = ESX.GetPlayerFromId(src)
jobKey = xPlayer.getJob().name
isBoss = (xPlayer.getJob().grade_name == 'boss')
end
MySQL.Async.fetchAll('SELECT * FROM spy_bodycam WHERE job = @job ORDER BY id DESC', {
['@job'] = jobKey
}, function(records)
TriggerClientEvent('spy-bodycam:client:refreshRecords', src, records, isBoss)
end)
end
end)
end)
RegisterNetEvent('spy-bodycam:server:showrecordingUI', function()
local src = source
local offJob
local jobKey
local isBoss
if Config.Framework == 'qb' then
local Player = QBCore.Functions.GetPlayer(src)
offJob = Player.PlayerData.job.label
jobKey = Player.PlayerData.job.name
isBoss = Player.PlayerData.job.isboss
else
local xPlayer = ESX.GetPlayerFromId(src)
offJob = xPlayer.getJob().label
jobKey = xPlayer.getJob().name
if xPlayer.getJob().grade_name == 'boss' then isBoss = true else isBoss = false end
end
MySQL.Async.fetchAll('SELECT * FROM spy_bodycam WHERE job = @job ORDER BY id DESC', {
['@job'] = jobKey
}, function(records)
TriggerClientEvent('spy-bodycam:client:openRecords', src, records, offJob, isBoss)
end)
end)
lib.addCommand('recordcam', {
help = 'Record bodycam footage.',
restricted = false
}, function(source, args, raw)
local src = source
if PlayerOnBodycam[src] then
local defwebhook
if Upload.ServiceUsed == 'discord' then
local jobKey
if Config.Framework == 'qb' then
local Player = QBCore.Functions.GetPlayer(src)
jobKey = Player.PlayerData.job.name
else
local xPlayer = ESX.GetPlayerFromId(src)
jobKey = xPlayer.getJob().name
end
if Upload.JobUploads[jobKey] then
defwebhook = Upload.JobUploads[jobKey].webhook
else
defwebhook = Upload.DefaultUploads.webhook
end
elseif Upload.ServiceUsed == 'fivemanage' or Upload.ServiceUsed == 'fivemerr' then
defwebhook = Upload.Token
end
TriggerClientEvent('spy-bodycam:client:startRec',src,defwebhook,Upload.ServiceUsed)
else
NotifyPlayerSv('Bodycam not turned on!','error',3000,src)
end
end)
function NotifyPlayerSv(msg,type,time,src)
if Config.Dependency.UseNotify == 'ox' then
TriggerClientEvent('ox_lib:notify', src, { type = type or "success", title = '', description = msg, duration = time })
elseif Config.Dependency.UseNotify == 'qb' then
TriggerClientEvent("QBCore:Notify", src, msg, type,time)
elseif Config.Dependency.UseNotify == 'esx' then
TriggerClientEvent("esx:showNotification", src, msg, type)
end
end
-- You can remove this if u want more optimization from the script.
-- If the vehicle is deleted or send to garage it removes it from the list.
Citizen.CreateThread(function()
while true do
for entityId, _ in pairs(CarsOnBodycam) do
local veh = NetworkGetEntityFromNetworkId(entityId)
if not DoesEntityExist(veh) then
CarsOnBodycam[entityId] = nil
GlobalState.CarsOnBodycam = CarsOnBodycam
end
end
Citizen.Wait(60000) -- Wait for 1 min before checking again
end
end)
-- Script Version Checker
local localVersion = GetResourceMetadata(GetCurrentResourceName(), 'version')
local fxManifestUrl = "https://raw.githubusercontent.com/Your-Spy/spy-bodycam/main/%5Bspy-bodycam%5D/spy-bodycam/fxmanifest.lua"
local function extractVersion(fxManifestContent)
for line in string.gmatch(fxManifestContent, "[^\r\n]+") do
if line:find("^version%s+'(.-)'$") then
return line:match("^version%s+'(.-)'$")
end
end
return nil
end
local function checkForUpdates()
PerformHttpRequest(fxManifestUrl, function(statusCode, response, headers)
print([[^4
____ ______ __ ____ ___ ______ ______ _ __ __
/ ___|| _ \ \ / / | __ ) / _ \| _ \ \ / / ___| / \ | \/ |
\___ \| |_) \ V / _____ | _ \| | | | | | \ V / | / _ \ | |\/| |
___) | __/ | | |_____| | |_) | |_| | |_| || || |___ / ___ \| | | |
|____/|_| |_| |____/ \___/|____/ |_| \____/_/ \_\_| |_|
]])
if statusCode == 200 then
local remoteVersion = extractVersion(response)
if remoteVersion and remoteVersion ~= localVersion then
print("^2NEW UPDATE: ^2" .. remoteVersion .. "^3 | ^1CURRENT: " .. localVersion.." ^9>> Download new version from github")
else
print("^2You are on latest version: ^2" .. localVersion)
end
else
print("^1Failed to check for updates. Status code: " .. statusCode)
end
end, "GET", "", {["Content-Type"] = "text/plain"})
end
AddEventHandler('onResourceStart', function(resourceName)
if GetCurrentResourceName() == resourceName then
checkForUpdates()
end
end)

View file

@ -0,0 +1,32 @@
Upload = Upload or {}
-- U have to start the recording using /recordcam and the recording will be uploaded to any of the service below.
Upload.ServiceUsed = 'fivemanage' -- discord | fivemanage | fivemerr
Upload.Token = 'c6sTqXXzOJksi7hKs0vMFuN8yUmJfcb1' -- fivemanage or fivemerr | [*note - for discord webhook is to be changed below not here]
-- FOR DISCORD LOGS
Upload.DiscordLogs = {
Enabled = false,
Username = 'Spy Bodycam Records', -- Bot Username
Title = 'Bodycam Records', -- Message Title
}
-- Upload Hooks if Upload.ServiceUsed = discord
Upload.DefaultUploads = { -- Default Upload of log if job not mentioned in Upload.JobUploads.
webhook = 'YOUR_WEBHOOK',
author = {
name = "Spy Bodycam",
icon_url = "https://i.imgur.com/tMyAdkz.png"
}
}
Upload.JobUploads = { -- Job Speific Uploads
['police'] = {
webhook = 'YOUR_WEBHOOK',
author = {
name = "Police Department",
icon_url = "https://i.imgur.com/tMyAdkz.png"
}
}, -- Add more here
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

View file

@ -0,0 +1,87 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Spy Bodycam</title>
<link rel="stylesheet" href="style.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.1/css/all.min.css">
</head>
<body>
<canvas style="display: none;" width="1280px" height="720px"></canvas>
<div class="overlayCont">
<div class="overlayBody">
<div class="bodyInfobox">
<div class="bodyDate"><i class="fa-solid fa-circle fa-fade bodyIcon"></i></i>08-04 23:58:45-0500</div>
<div class="bodyNum">BODY 22 X6070511N</div>
<div class="bodyCallsign">(222) Spy Dev</div>
</div>
<img src="images/brand_logo.png" alt="">
<div class="AddonIcons"><i class="fa-solid fa-battery-three-quarters"></i><i class="fa-solid fa-wifi"></i></div>
</div>
</div>
<div class="RecordInfo">
<div class="recordBox">
<div class="HeadText"><i class="fa-solid fa-video fa-fade bodyIcon"></i>Recording Started</div>
</div>
</div>
<div class="currWatchCont">
<div class="watchInfo">
<div class="userLaber">
<i class="fa-solid fa-video watchIcon" style="color: #cf0707;"></i>
<span class="watchInText typeCam">Bodycam</span>
<br>
<span class="watchInText plateText">Plate : 726G21</span>
<br>
<span class="watchInText carText">Car : Crown Vic</span>
</div>
</div>
<div class="backInfo">
<div class="backLabel"><i class="fa-solid fa-video-slash backIcon"></i><span class="backInText">E</span></div>
</div>
</div>
<div class="recCont">
<div class="recMain">
<div class="recTitle">
<div class="recTitleTxt">Bodycam Recordings</div>
<div class="recDesc">POLICE DATABASE</div>
<img src="images/brand_logo.png" alt="" class="recLogo">
</div>
<div class="RecBox">
<div class="recInnerScroll"></div>
</div>
<div class="recFilters">
<div class="recSearch">
<div class="searchText"><i class="fas fa-search"></i></div>
<input type="text" class="searchInput" placeholder="Seach Footage...">
</div>
<div class="recDateF">
<input type="date" class="selectedDate" placeholder="Select Date">
</div>
</div>
</div>
</div>
<div class="vidplaycont">
<div class="vidMain">
<video class="vidPlayer" controls autoplay loop>
<source src="https://cdn.discordapp.com/attachments/1257923291512045568/1263565598277763082/video.webm?ex=669ab2aa&is=6699612a&hm=a6afae0dd5179e186a56fecdb506a85f3769ad9db5b33ec14efede37454a1ba7&" type="video/webm">
</video>
<div class="vidPlayerIcon"><i class="fa-solid fa-circle-xmark"></i></div>
</div>
</div>
<div class="askMain">
<div class="askIn">
<div class="askIcon"><i class="fa-solid fa-trash fa-bounce"></i></div>
<h1 class="askTitle">Are you sure?</h1>
<div class="askBtnCont">
<div class="askBtn">Yes</div>
<div class="askBtn">No</div>
</div>
</div>
</div>
<audio id="beep-sound" src="audio/on_sound.mp3"></audio>
<audio id="off-sound" src="audio/off_sound.mp3"></audio>
<script src="js/jquery-3.7.1.min.js"></script>
<script type="module" src="js/app.js"></script>
</body>
</html>

View file

@ -0,0 +1,394 @@
import { GameView } from './gameview.js';
const gameview = new GameView();
let isRecording = false;
let recordingTimeout;
$(document).ready(function () {
$('.overlayCont').hide();
$('.currWatchCont').hide();
$('.RecordInfo').hide();
$('.recCont').hide();
$('.askMain').hide();
$('.vidplaycont').hide();
const beepSound = document.getElementById('beep-sound');
const offSound = document.getElementById('off-sound');
function updateTime() {
const date = new Date();
date.setUTCHours(date.getUTCHours() - 5);
const month = ("0" + (date.getMonth() + 1)).slice(-2);
const day = ("0" + date.getDate()).slice(-2);
const hours = ("0" + date.getHours()).slice(-2);
const minutes = ("0" + date.getMinutes()).slice(-2);
const seconds = ("0" + date.getSeconds()).slice(-2);
const gameTime = `${month}-${day} ${hours}:${minutes}:${seconds}-0500`;
$('.bodyDate').html('<i class="fa-solid fa-circle fa-fade bodyIcon"></i>' + gameTime);
}
let interval;
window.addEventListener('message', function (event) {
const data = event.data;
if (data.action === 'open') {
updateTime();
interval = setInterval(updateTime, 1000);
$('.bodyNum').text(data.bodyname);
$('.bodyCallsign').text(data.callsign);
$('.overlayCont').removeClass('popOut').addClass('popIn').show();
beepSound.play();
} else if (data.action === 'close') {
clearInterval(interval);
$('.overlayCont').removeClass('popIn').addClass('popOut').one('animationend', function () {
$(this).hide();
$(this).removeClass('popOut');
});
offSound.play();
}
if (data.action === 'openWatch') {
let typeCam;
let plateText;
let carText;
if (data.debug) {
var controlsHtml = `
<span class="watchInText typeCam">Controls</span>
<br>
<span class="watchInText typeCam">W -> Up</span>
<br>
<span class="watchInText typeCam">S -> Down</span>
<br>
<span class="watchInText typeCam">A -> Left</span>
<br>
<span class="watchInText typeCam">D -> Right</span>
<br>
<span class="watchInText typeCam">Q -> Forward</span>
<br>
<span class="watchInText typeCam">E -> Backward</span>
`;
document.querySelector('.userLaber').innerHTML = controlsHtml;
} else {
if (data.isbodycam) {
typeCam = "BODYCAM";
plateText = "CamID: " + data.bodyId;
carText = "Name: " + data.name;
} else {
typeCam = "DASHCAM";
plateText = "Plate: " + data.bodyId;
carText = "Car: " + data.name;
}
document.querySelector('.typeCam').textContent = typeCam;
document.querySelector('.plateText').textContent = plateText;
document.querySelector('.carText').textContent = carText;
}
$('.backInText').text(data.exitKey);
$('.currWatchCont').fadeIn();
} else if (data.action === 'closeWatch') {
$('.currWatchCont').fadeOut();
}
if (data.action === 'toggle_record') {
clearTimeout(recordingTimeout);
if (!isRecording) {
// Start recording
isRecording = true;
$('.HeadText').html('<i class="fa-solid fa-circle fa-fade bodyIcon" style="color: rgb(173, 8, 8);"></i>' + 'Recording Started');
$('.RecordInfo').fadeIn();
startRecording(data.hook, data.service);
recordingTimeout = setTimeout(() => {
if (isRecording) {
// Stop recording automatically after 30 seconds
isRecording = false;
$('.HeadText').html('<i class="fa-solid fa-circle bodyIcon" style="color: white;"></i>' + 'Recording Stopped');
setTimeout(() => {
$('.RecordInfo').fadeOut();
}, 2000);
stopRecording();
}
}, data.recTiming * 1000);
} else {
// Stop recording
isRecording = false;
$('.HeadText').html('<i class="fa-solid fa-circle bodyIcon" style="color: white;"></i>' + 'Recording Stopped');
clearTimeout(recordingTimeout);
setTimeout(() => {
$('.RecordInfo').fadeOut();
}, 2000);
stopRecording();
}
}
if (data.action === 'cancel_rec_force') {
if (isRecording) {
// Force stop recording
isRecording = false;
$('.HeadText').html('<i class="fa-solid fa-circle bodyIcon" style="color: white;"></i>' + 'Recording Stopped');
clearTimeout(recordingTimeout);
setTimeout(() => {
$('.RecordInfo').fadeOut();
}, 1000);
stopRecording();
}
}
// RECORDS SHOWING
if (data.action === 'show_records') {
$('.recInnerScroll').empty();
$('.recDesc').text(`${data.jobTitle} Database`);
if (Array.isArray(data.recordData) && data.recordData.length > 0) {
$.each(data.recordData, function (index, record) {
var camBox = $(
`
<div class="camBox">
<div class="camInfo">
<div class="camTitle">${record.playername}<span class="camStreet"> [${record.street}]</span></div>
<div class="camDesc">Date: ${record.date}</div>
</div>
<div class="camIcons">
<div class="camShow" data-stored="${record.videolink}"><i class="fa-solid fa-eye"></i></div>
${data.isBoss ? `<div class="camDelete"><i class="fa-solid fa-trash"></i></div>` : ''}
</div>
</div>
`
);
$('.recInnerScroll').append(camBox);
});
$('.recCont').show();
} else {
$('.recInnerScroll').html('<h1 class="noRecAv">No records available</h1>');
$('.recCont').show();
}
}
// Refresh Records
if (data.action === 'refreshrec') {
$('.recInnerScroll').empty();
if (Array.isArray(data.recordData) && data.recordData.length > 0) {
$.each(data.recordData, function (index, record) {
var camBox = $(
`
<div class="camBox">
<div class="camInfo">
<div class="camTitle">${record.playername}<span class="camStreet"> [${record.street}]</span></div>
<div class="camDesc">Date: ${record.date}</div>
</div>
<div class="camIcons">
<div class="camShow" data-stored="${record.videolink}"><i class="fa-solid fa-eye"></i></div>
${data.isBoss ? `<div class="camDelete"><i class="fa-solid fa-trash"></i></div>` : ''}
</div>
</div>
`
);
$('.recInnerScroll').append(camBox);
});
$('.recCont').show();
} else {
$('.recInnerScroll').html('<h1 class="noRecAv">No records available</h1>');
$('.recCont').show();
}
}
});
let deleteUrl = '';
// SEARCH AND DATE FILTERS :)
function updateVisibility(searchText) {
$('.camBox').each(function () {
var camTitleText = $(this).find('.camTitle').text().toLowerCase();
var camDescDate = $(this).find('.camDesc').text().trim().replace('Date: ', '');
if ((searchText === '' || camTitleText.includes(searchText)) &&
($(this).css('display') !== 'none' || camDescDate === $('.selectedDate').val())
) {
$(this).show();
}
else if ((searchText === '' || camTitleText.includes(searchText)) &&
($('.selectedDate').val() === '')
) {
$(this).show();
} else {
$(this).hide();
}
});
}
$('.searchInput').on('input', function () {
var searchText = $(this).val().toLowerCase();
updateVisibility(searchText);
});
$('.selectedDate').on('change', function () {
var selectedDate = $(this).val();
$('.camBox').each(function () {
var camDescDate = $(this).find('.camDesc').text().trim().replace('Date: ', '');
if (selectedDate === '') {
$(this).show();
} else if (selectedDate === camDescDate) {
$(this).show();
} else {
$(this).hide();
}
});
var searchText = $('.searchInput').val().toLowerCase();
updateVisibility(searchText);
});
$(document).on('click', '.camDelete', function () {
const camBox = $(this).closest('.camBox');
deleteUrl = camBox.find('.camShow').data('stored');
$('.askMain').fadeIn();
});
$(document).on('click', '.askBtn:nth-child(1)', function () {
$('.askMain').fadeOut();
if (deleteUrl) {
$.post(`https://${GetParentResourceName()}/deleteVideo`, JSON.stringify({
vidurl: deleteUrl
}));
}
});
$(document).on('click', '.askBtn:nth-child(2)', function () {
$('.askMain').fadeOut();
});
$(document).on('click', '.camShow', function () {
var videoUrl = $(this).data('stored');
$('.vidPlayer').attr('src', videoUrl);
$('.vidplaycont').show();
});
$(document).on('click', '.vidPlayerIcon', function () {
$('.vidplaycont').hide();
$('.vidPlayer').attr('src', '');
});
$(document).on('click', function (event) {
if ($(event.target).is('.vidplaycont')) {
$('.vidplaycont').hide();
$('.vidPlayer').attr('src', '');
}
});
$(document).on('keydown', function (e) {
if (e.which === 27) {
$('.askMain').hide();
$('.searchInput').val('');
$('.selectedDate').val('');
$('.recCont').hide();
$('.vidplaycont').hide();
$('.vidPlayer').attr('src', '');
$.post(`https://${GetParentResourceName()}/closeRecUI`, '{}');
}
});
});
let mediaRecorder;
let audioStream;
const canvasElement = document.querySelector('canvas');
async function uploadBlob(videoBlob, hook, service) {
$.post(`https://${GetParentResourceName()}/exitBodyCam`, '{}');
const formData = new FormData();
try {
let response, responseData;
if (service === 'fivemanage') {
formData.append('video', videoBlob);
response = await fetch('https://api.fivemanage.com/api/video', {
method: 'POST',
headers: {
Authorization: hook,
},
body: formData,
});
if (!response.ok) {
throw new Error(`Failed to upload video to FiveManage: ${response.status}`);
}
responseData = await response.json();
$.post(`https://${GetParentResourceName()}/videoLog`, JSON.stringify({
vidurl: responseData.url
}));
} else if (service === 'fivemerr') {
formData.append('file', videoBlob, 'video.webm');
response = await fetch('https://api.fivemerr.com/v1/media/videos', {
method: 'POST',
headers: {
Authorization: hook,
},
body: formData,
});
if (!response.ok) {
throw new Error(`Failed to upload video to Fivemerr: ${response.status}`);
}
responseData = await response.json();
$.post(`https://${GetParentResourceName()}/videoLog`, JSON.stringify({
vidurl: responseData.url
}));
} else if (service === 'discord') {
formData.append('file', videoBlob, 'video.webm');
response = await fetch(hook, {
method: 'POST',
body: formData,
});
if (!response.ok) {
throw new Error(`Failed to upload video to Discord: ${response.status}`);
}
responseData = await response.json();
$.post(`https://${GetParentResourceName()}/videoLog`, JSON.stringify({
vidurl: responseData.attachments[0].url
}));
}
} catch (error) {
console.error('^1[ERROR]:^3', error.message);
}
}
async function startMicrophoneCapture() {
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
return stream;
} catch (error) {
console.error('Microphone capture failed. Recording video only.');
return null;
}
}
async function startRecording(hook, service) {
audioStream = await startMicrophoneCapture();
if (!isRecording){
if (audioStream) {
audioStream.getAudioTracks().forEach(track => track.stop());
}
return
}
console.log('Video Recording Started');
const gameView = gameview.createGameView(canvasElement);
const canvasStream = canvasElement.captureStream(30);
// Combine audio and video streams
const combinedStream = new MediaStream([
...canvasStream.getVideoTracks(),
...(audioStream ? audioStream.getAudioTracks() : [])
]);
const videoChunks = [];
window.gameView = gameView;
mediaRecorder = new MediaRecorder(combinedStream, { mimeType: 'video/webm;codecs=vp9' });
mediaRecorder.start();
mediaRecorder.ondataavailable = (e) => e.data.size > 0 && videoChunks.push(e.data);
mediaRecorder.onstop = async () => {
const videoBlob = new Blob(videoChunks, { type: 'video/webm' });
if (videoBlob.size > 0) {
uploadBlob(videoBlob, hook, service);
}
if (audioStream) {
audioStream.getAudioTracks().forEach(track => track.stop());
}
};
}
function stopRecording() {
if (mediaRecorder && mediaRecorder.state === 'recording') {
mediaRecorder.stop();
}
}

View file

@ -0,0 +1,175 @@
export class GameView {
constructor() {
this.vertexShaderSrc = `
attribute vec2 a_position;
attribute vec2 a_texcoord;
uniform mat3 u_matrix;
varying vec2 textureCoordinate;
void main() {
gl_Position = vec4(a_position, 0.0, 1.0);
textureCoordinate = a_texcoord;
}
`;
this.fragmentShaderSrc = `
varying highp vec2 textureCoordinate;
uniform sampler2D external_texture;
void main()
{
gl_FragColor = texture2D(external_texture, textureCoordinate);
}
`;
this.interval = null;
}
makeShader = (gl, type, src) => {
const shader = gl.createShader(type);
gl.shaderSource(shader, src);
gl.compileShader(shader);
return shader;
}
createTexture(gl) {
const tex = gl.createTexture();
const texPixels = new Uint8Array([0, 0, 255, 255]);
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texImage2D(
gl.TEXTURE_2D,
0,
gl.RGBA,
1,
1,
0,
gl.RGBA,
gl.UNSIGNED_BYTE,
texPixels,
);
gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
// Magic hook sequence
gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.MIRRORED_REPEAT);
gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);
// Reset
gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
return tex;
}
createBuffers = (gl) => {
const vertexBuff = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuff);
gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]),
gl.STATIC_DRAW,
);
const texBuff = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, texBuff);
gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array([0, 0, 1, 0, 0, 1, 1, 1]),
gl.STATIC_DRAW,
);
return { vertexBuff, texBuff };
}
createProgram = (gl) => {
const vertexShader = this.makeShader(gl, gl.VERTEX_SHADER, this.vertexShaderSrc);
const fragmentShader = this.makeShader(gl, gl.FRAGMENT_SHADER, this.fragmentShaderSrc);
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
gl.useProgram(program);
const vloc = gl.getAttribLocation(program, 'a_position');
const tloc = gl.getAttribLocation(program, 'a_texcoord');
return { program, vloc, tloc };
}
createStuff(gl) {
const tex = this.createTexture(gl);
const { program, vloc, tloc } = this.createProgram(gl);
const { vertexBuff, texBuff } = this.createBuffers(gl);
gl.useProgram(program);
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.uniform1i(gl.getUniformLocation(program, 'external_texture'), 0);
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuff);
gl.vertexAttribPointer(vloc, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(vloc);
gl.bindBuffer(gl.ARRAY_BUFFER, texBuff);
gl.vertexAttribPointer(tloc, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(tloc);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
}
render(gl, gameView) {
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
gl.finish();
let render = () => {};
gameView.animationFrame = requestAnimationFrame(render);
}
createGameView = (canvas) => {
this.canvas = canvas;
const gl = this.canvas.getContext('webgl', {
antialias: false,
depth: false,
stencil: false,
alpha: false,
desynchronized: true,
failIfMajorPerformanceCaveat: false,
});
const gameView = {
canvas,
gl,
animationFrame: undefined,
resize: (width, height) => {
gl.viewport(0, 0, width, height);
gl.canvas.width = width;
gl.canvas.height = height;
},
};
this.createStuff(gl);
this.interval = setInterval(() => {
this.render(gl, gameView);
}, 0);
return gameView;
}
stop() {
if (this.canvas) {
if (this.canvas.style.display != "none") {
this.canvas.style.display = "none";
}
if (this.interval) {
clearInterval(this.interval);
}
}
}
}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,451 @@
@import url('https://fonts.googleapis.com/css2?family=Varela+Round&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Play:wght@400;700&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Fjalla+One&family=PT+Sans+Narrow:wght@400;700&family=Play:wght@400;700&display=swap');
* {
box-sizing: border-box;
margin: 0%;
padding: 0%;
user-select: none;
}
.overlayCont{
display: flex;
justify-content: flex-end;
}
.overlayBody {
position: relative;
display: flex;
flex-direction: row;
margin-top: 2rem;
margin-right: 2rem;
background: rgba(0, 0, 0, 0.486);
box-shadow: -2px 0px 46px -9px rgba(0, 0, 0, 0.644) inset, 0px 4px 8px rgba(0, 0, 0, 0.418);
padding: 1rem;
border-radius: 10px;
color: white;
}
.RecordInfo{
display: flex;
margin-top: 10px;
justify-content: flex-end;
}
.recordBox{
margin-top: 10px;
margin-right: 2rem;
font-family: "Play", sans-serif;
background: rgba(0, 0, 0, 0.486);
box-shadow: -2px 0px 46px -9px rgba(161, 2, 2, 0.644) inset, 0px 4px 8px rgba(0, 0, 0, 0.418);
color: white;
padding: 1rem;
font-size: 20px;
border-radius: 10px;
}
.bodyInfobox {
display: flex;
flex-direction: column;
align-items: flex-end;
}
.overlayBody img{
width: 66px;
height: auto;
}
.bodyNum , .bodyCallsign {
font-family: "Play", sans-serif;
}
.bodyIcon{
margin-right: 15px;
color: rgb(173, 8, 8);
}
.bodyDate{
font-size: 17px;
font-family: "Varela Round", sans-serif;
font-weight: bold;
}
.AddonIcons{
display: flex;
gap: 5px;
position: absolute;
bottom: 9px;
left: 20px;
font-size: 15px;
}
.currWatchCont{
position: absolute;
left: 0px;
top: 0px;
display: flex;
flex-direction: row;
}
.watchInText{
margin-left: 10px;
}
.watchIcon{
font-size: 15px;
}
.userLaber {
margin-top: 10px;
margin-left: 10px;
font-family: "Play", sans-serif;
font-size: 20px;
display: inline-block;
padding: 10px;
border-radius: 10px;
color: white;
text-shadow: -1px 1px 1px rgba(0, 0, 0, 0.55);
background: rgba(0, 0, 0, 0.397);
box-shadow: -2px 0px 46px -9px rgba(0, 0, 0, 0.57) inset,0px 4px 8px rgba(0, 0, 0, 0.2);
}
.backInText{
background: rgb(27, 27, 27);
padding: 4px;
border-radius: 5px;
margin-left: 10px;
}
.backIcon{
font-size: 15px;
}
.backLabel {
margin-top: 10px;
margin-left: 10px;
font-family: "Play", sans-serif;
font-size: 20px;
display: inline-block;
padding: 10px;
border-radius: 10px;
color: white;
text-shadow: -1px 1px 1px rgba(0, 0, 0, 0.55);
background: rgba(0, 0, 0, 0.397);
box-shadow: -2px 0px 46px -9px rgba(0, 0, 0, 0.57) inset,0px 4px 8px rgba(0, 0, 0, 0.2);
}
@keyframes popIn {
0% {
transform: scale(0.8);
opacity: 0;
}
100% {
transform: scale(1);
opacity: 1;
}
}
@keyframes popOut {
0% {
transform: scale(1);
opacity: 1;
}
100% {
transform: scale(0.8);
opacity: 0;
}
}
.popIn {
animation: popIn 0.5s forwards;
}
.popOut {
animation: popOut 0.5s forwards;
}
.recCont{
position: absolute;
left: 10%;
top: 10%;
display: flex;
justify-content: center;
align-items: center;
}
.recMain{
position: relative;
display: flex;
flex-direction: column;
width: 80vw;
height: 80vh;
color: white;
}
.recTitleTxt{
font-family: "Bebas Neue", sans-serif;
font-style: italic;
font-size: 3rem;
}
.recTitle img{
position: absolute;
margin-top: 9px;
margin-right: 20px;
width: 100px;
right: 0%;
top: 0%;
}
.recTitle{
padding-top: 10px;
padding-left: 20px;
padding-bottom: 0.5rem;
background: #353b48;
box-shadow: -2px 0px 46px -9px rgba(0, 0, 0, 0.644) inset, 0px 4px 8px rgba(0, 0, 0, 0.418);
border-top-left-radius: 5px;
border-top-right-radius: 5px;
}
.recDesc{
font-family: "PT Sans Narrow", sans-serif;
font-style: italic;
font-size: 1.3rem;
}
.RecBox {
display: flex;
width: 100%;
height: 63vh;
background: #101522;
box-shadow: -1px 15px 13px -1px rgba(0,0,0,0.5) inset, 0px -13px 13px -1px rgba(0,0,0,0.5) inset;
}
.recInnerScroll{
display: flex;
align-self: center;
align-items: center;
flex-direction: column;
gap: 15px;
width: 100%;
height: 55vh;
overflow-x: hidden;
overflow-y: auto;
}
.recInnerScroll::-webkit-scrollbar {
width: 3px;
}
.recInnerScroll::-webkit-scrollbar-thumb {
background: #353b48;
border-radius: 10px;
}
.recInnerScroll::-webkit-scrollbar-thumb:hover {
background: #242931;
}
.camBox{
display: flex;
justify-content: space-between;
align-items: center;
width: 90%;
background: linear-gradient(180deg,#353b48 0%, #23272f 80%);
box-shadow: 2px 2px 13px 7px rgba(0, 0, 0, 0.171) inset, -1px 7px 13px -4px rgba(0,0,0,0.42);
border-radius: 3px;
padding: 10px;
border: 1px solid #c9cacc18;
transition: all 0.3s ease;
}
.camShow:hover{
color: #ffffff;
text-shadow: 4px 1px 5px rgba(0, 0, 0, 0.315);
}
.camDelete:hover{
color: #ca3333;
text-shadow: 4px 1px 5px rgba(0, 0, 0, 0.315);
}
.camBox:hover{
box-shadow: 2px 2px 13px 7px rgba(0, 0, 0, 0.411) inset,-1px 9px 13px -4px rgba(0,0,0,0.5);
border: 1px solid #c9cacc3f;
}
.camIcons{
color: #999999;
display:flex ;
align-items: center;
gap: 20px;
font-size: 1.4rem;
text-shadow: 4px 1px 5px rgba(0, 0, 0, 0.123);
cursor: pointer;
}
.camDesc{
font-family: "Play", sans-serif;
}
.camTitle{
font-size: 1.4rem;
font-family: "Fjalla One", sans-serif;
}
.camStreet{
color: rgb(170, 170, 170);
font-size: 0.9rem;
font-family: "Fjalla One", sans-serif;
font-style: italic;
}
.recFilters {
display: flex;
justify-content: space-between;
align-items: center;
height: 5rem;
padding: 0 1rem;
color: white;
border-bottom-left-radius: 5px;
border-bottom-right-radius: 5px;
background-color: #353b48;
box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.418);
}
.recSearch {
display: flex;
align-items: center;
gap: 0.5rem;
}
.searchText i {
margin-right: 0.5rem;
}
.searchInput {
height: 2rem;
padding: 0.5rem;
border: none;
border-radius: 5px;
outline: none;
background-color: #2f3640;
color: white;
font-size: 1rem;
font-family: "Play", sans-serif;
}
.recDateF {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 1rem;
color: white;
font-family: "Play", sans-serif;
}
.selectedDate {
padding: 0.5rem;
border: none;
border-radius: 5px;
outline: none;
background-color: #2f3640;
font-size: 1rem;
font-family: "Play", sans-serif;
color: white;
}
.selectedDate::-webkit-calendar-picker-indicator {
filter: invert(1);
}
.vidplaycont {
position: absolute;
display: flex;
justify-content: center;
align-items: center;
width: 100vw;
height: 100vh;
top: 0;
left: 0;
}
.vidMain {
position: relative;
display: flex;
justify-content: center;
align-items: center;
background: linear-gradient(180deg,#353b48 0%, #23272f 80%);
border-radius: 5px;
width: 63vw;
height: 60vh;
padding: 1rem;
margin-top: 2rem;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.5);
}
.vidPlayer {
max-width: 100%;
max-height: 100%;
border-radius: 10px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
object-fit: contain;
}
.vidPlayerIcon {
position: absolute;
color: rgb(255, 255, 255);
font-size: 1.5rem;
top: 19px;
right: 17px;
cursor: pointer;
transition: color 0.3s ease;
}
.vidPlayerIcon:hover {
color: #ca3333;
}
.askMain{
position: absolute;
display: flex;
justify-content: center;
align-items: center;
width: 80vw;
height: 80vh;
left: 10%;
top: 10%;
}
.askIn{
background: linear-gradient(180deg,#353b48 0%, #23272f 80%);
box-shadow: 2px 2px 13px 7px rgba(0, 0, 0, 0.171) inset, -1px 7px 13px -4px rgba(0,0,0,0.42);
border: 1px solid #c9cacc18;
padding: 2rem;
border-radius: 10px;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
transition: all 0.3s ease;
}
.askIn:hover{
box-shadow: 2px 2px 13px 7px rgba(0, 0, 0, 0.411) inset,-1px 9px 13px -4px rgba(0,0,0,0.5);
border: 1px solid #c9cacc3f;
}
.askIcon{
font-size: 1.4rem;
color: #ca3333;
margin-bottom: 0.2rem;
}
.askBtnCont{
display: flex;
justify-content: space-around;
width: 100%;
gap: 10px;
}
.askBtn{
color: white;
font-size: 1.4rem;
font-family: "Play", sans-serif;
background: #1015225d;
border-radius: 5px;
padding: 10px;
cursor: pointer;
}
.askBtn:hover{
background: #101522bb;
}
.askTitle{
color: white;
font-size: 1.4rem;
font-family: "Fjalla One", sans-serif;
margin: 1rem 0;
}
.noRecAv{
color: rgba(255, 255, 255, 0.26);
font-size: 3rem;
font-family: "Fjalla One", sans-serif;
}

View file

@ -0,0 +1,7 @@
fx_version 'cerulean'
game 'gta5'
lua54 'yes'
author "aarjey0_0"
data_file 'DLC_ITYP_REQUEST' 'stream/rj_bodycam.ytyp'