mirror of
https://github.com/citizenfx/cfx-server-data.git
synced 2025-12-12 06:14:09 +01:00
add runcode resource
This commit is contained in:
8
resources/runcode/__resource.lua
Normal file
8
resources/runcode/__resource.lua
Normal file
@@ -0,0 +1,8 @@
|
||||
client_script 'runcode_cl.lua'
|
||||
server_script 'runcode_sv.lua'
|
||||
server_script 'runcode_web.lua'
|
||||
|
||||
client_script 'runcode_shared.lua'
|
||||
server_script 'runcode_shared.lua'
|
||||
|
||||
resource_manifest_version '44febabe-d386-4d18-afbe-5e627f4af937'
|
||||
3
resources/runcode/runcode.lua
Normal file
3
resources/runcode/runcode.lua
Normal file
@@ -0,0 +1,3 @@
|
||||
RegisterCommand('runcode', function(source, args, rawCommand)
|
||||
RunCode('return ' .. rawCommand:sub(8))
|
||||
end, true)
|
||||
15
resources/runcode/runcode_cl.lua
Normal file
15
resources/runcode/runcode_cl.lua
Normal file
@@ -0,0 +1,15 @@
|
||||
RegisterNetEvent('runcode:gotSnippet')
|
||||
|
||||
AddEventHandler('runcode:gotSnippet', function(id, code)
|
||||
local res, err = RunCode(code)
|
||||
|
||||
if not err then
|
||||
if type(res) == 'vector3' then
|
||||
res = json.encode({ table.unpack(res) })
|
||||
elseif type(res) == 'table' then
|
||||
res = json.encode(res)
|
||||
end
|
||||
end
|
||||
|
||||
TriggerServerEvent('runcode:gotResult', id, res, err)
|
||||
end)
|
||||
17
resources/runcode/runcode_shared.lua
Normal file
17
resources/runcode/runcode_shared.lua
Normal file
@@ -0,0 +1,17 @@
|
||||
function RunCode(code)
|
||||
local code, err = load(code, '@runcode')
|
||||
|
||||
if err then
|
||||
print(err)
|
||||
return nil, err
|
||||
end
|
||||
|
||||
local status, result = pcall(code)
|
||||
print(result)
|
||||
|
||||
if status then
|
||||
return result
|
||||
else
|
||||
return nil, result
|
||||
end
|
||||
end
|
||||
7
resources/runcode/runcode_sv.lua
Normal file
7
resources/runcode/runcode_sv.lua
Normal file
@@ -0,0 +1,7 @@
|
||||
RegisterCommand('run', function(source, args, rawCommand)
|
||||
local res, err = RunCode('return ' .. rawCommand:sub(4))
|
||||
end, true)
|
||||
|
||||
RegisterCommand('crun', function(source, args, rawCommand)
|
||||
TriggerClientEvent('runcode:gotSnippet', source, -1, 'return ' .. rawCommand:sub(5))
|
||||
end, true)
|
||||
133
resources/runcode/runcode_web.lua
Normal file
133
resources/runcode/runcode_web.lua
Normal file
@@ -0,0 +1,133 @@
|
||||
local cachedFiles = {}
|
||||
|
||||
local function sendFile(res, fileName)
|
||||
if cachedFiles[fileName] then
|
||||
res.send(cachedFiles[fileName])
|
||||
return
|
||||
end
|
||||
|
||||
local fileData = LoadResourceFile(GetCurrentResourceName(), 'web/' .. fileName)
|
||||
|
||||
if not fileData then
|
||||
res.writeHead(404)
|
||||
res.send('Not found.')
|
||||
return
|
||||
end
|
||||
|
||||
cachedFiles[fileName] = fileData
|
||||
res.send(fileData)
|
||||
end
|
||||
|
||||
local codeId = 1
|
||||
local codes = {}
|
||||
|
||||
local function handlePost(req, res)
|
||||
req.setDataHandler(function(body)
|
||||
local data = json.decode(body)
|
||||
|
||||
if not data or not data.password or not data.code then
|
||||
res.send(json.encode({ error = 'Bad request.'}))
|
||||
return
|
||||
end
|
||||
|
||||
if GetConvar('rcon_password', '') == '' then
|
||||
res.send(json.encode({ error = 'The server has an empty rcon_password.'}))
|
||||
return
|
||||
end
|
||||
|
||||
if data.password ~= GetConvar('rcon_password', '') then
|
||||
res.send(json.encode({ error = 'Bad password.'}))
|
||||
return
|
||||
end
|
||||
|
||||
if not data.client or data.client == '' then
|
||||
CreateThread(function()
|
||||
local result, err = RunCode(data.code)
|
||||
|
||||
res.send(json.encode({
|
||||
result = result,
|
||||
error = err
|
||||
}))
|
||||
end)
|
||||
else
|
||||
codes[codeId] = {
|
||||
timeout = GetGameTimer() + 1000,
|
||||
res = res
|
||||
}
|
||||
|
||||
TriggerClientEvent('runcode:gotSnippet', tonumber(data.client), codeId, data.code)
|
||||
|
||||
codeId = codeId + 1
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
local function returnCode(id, res, err)
|
||||
if not codes[id] then
|
||||
return
|
||||
end
|
||||
|
||||
local code = codes[id]
|
||||
codes[id] = nil
|
||||
|
||||
local gotFrom
|
||||
|
||||
if source then
|
||||
gotFrom = GetPlayerName(source) .. ' [' .. tostring(source) .. ']'
|
||||
end
|
||||
|
||||
code.res.send(json.encode({
|
||||
result = res,
|
||||
error = err,
|
||||
from = gotFrom
|
||||
}))
|
||||
end
|
||||
|
||||
CreateThread(function()
|
||||
while true do
|
||||
Wait(100)
|
||||
|
||||
for k, v in ipairs(codes) do
|
||||
if GetGameTimer() > v.timeout then
|
||||
source = nil
|
||||
returnCode(k, '', 'Timed out waiting on the target client.')
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
RegisterNetEvent('runcode:gotResult')
|
||||
AddEventHandler('runcode:gotResult', returnCode)
|
||||
|
||||
SetHttpHandler(function(req, res)
|
||||
local path = req.path
|
||||
|
||||
if req.method == 'POST' then
|
||||
return handlePost(req, res)
|
||||
end
|
||||
|
||||
-- client shortcuts
|
||||
if req.path == '/clients' then
|
||||
local clientList = {}
|
||||
|
||||
for _, id in ipairs(GetPlayers()) do
|
||||
table.insert(clientList, { GetPlayerName(id), id })
|
||||
end
|
||||
|
||||
res.send(json.encode({
|
||||
clients = clientList
|
||||
}))
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
-- should this be the index?
|
||||
if req.path == '/' then
|
||||
path = 'index.html'
|
||||
end
|
||||
|
||||
-- remove any '..' from the path
|
||||
path = path:gsub("%.%.", "")
|
||||
|
||||
return sendFile(res, path)
|
||||
end)
|
||||
97
resources/runcode/web/index.html
Normal file
97
resources/runcode/web/index.html
Normal file
@@ -0,0 +1,97 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>fivem runcode</title>
|
||||
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
|
||||
|
||||
<style type="text/css">
|
||||
body {
|
||||
font-family: "Segoe UI", sans-serif;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="code-container" style="width:800px;height:600px;border:1px solid grey"></div><br>
|
||||
Password: <input type="password" id="password"> (use your rcon password)<br>
|
||||
Client ID: <input type="text" id="client"> (leave blank for server)<br>
|
||||
<div id="clients"></div>
|
||||
<button id="run">Run</button>
|
||||
<div id="result">
|
||||
</div>
|
||||
|
||||
<!--
|
||||
to use a local deployment, uncomment; do note currently the server isn't optimized to serve >1MB files
|
||||
<script src="monaco-editor/vs/loader.js"></script>
|
||||
-->
|
||||
|
||||
<script src="https://unpkg.com/monaco-editor@0.10.0/min/vs/loader.js"></script>
|
||||
|
||||
<script>
|
||||
function fetchClients() {
|
||||
fetch('/runcode/clients').then(res => res.json()).then(res => {
|
||||
const el = document.querySelector('#clients');
|
||||
el.innerHTML = '';
|
||||
|
||||
const clients = res.clients;
|
||||
clients.push(['All', -1]);
|
||||
|
||||
for (const client of clients) {
|
||||
const l = document.createElement('a');
|
||||
l.addEventListener('click', e => {
|
||||
document.querySelector('#client').value = client[1];
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
l.setAttribute('href', 'javascript:void(0)');
|
||||
l.appendChild(document.createTextNode(client[0]));
|
||||
|
||||
el.appendChild(l);
|
||||
el.appendChild(document.createTextNode(' '));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setInterval(() => fetchClients(), 1000);
|
||||
|
||||
require.config({ paths: { 'vs': 'https://unpkg.com/monaco-editor@0.10.0/min/vs' }});
|
||||
require(['vs/editor/editor.main'], function() {
|
||||
const editor = monaco.editor.create(document.getElementById('code-container'), {
|
||||
value: 'return 42',
|
||||
language: 'lua'
|
||||
});
|
||||
|
||||
document.querySelector('#run').addEventListener('click', e => {
|
||||
const text = editor.getValue();
|
||||
|
||||
fetch('/runcode/', {
|
||||
method: 'post',
|
||||
body: JSON.stringify({
|
||||
password: document.querySelector('#password').value,
|
||||
client: document.querySelector('#client').value,
|
||||
code: text
|
||||
})
|
||||
}).then(res => res.json()).then(res => {
|
||||
const resultElement = document.querySelector('#result');
|
||||
|
||||
if (res.error) {
|
||||
resultElement.style.color = '#aa0000';
|
||||
} else {
|
||||
resultElement.style.color = '#000000';
|
||||
}
|
||||
|
||||
resultElement.innerHTML = res.error || res.result;
|
||||
|
||||
if (res.from) {
|
||||
resultElement.innerHTML += ' (from ' + res.from + ')';
|
||||
}
|
||||
});
|
||||
|
||||
e.preventDefault();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user