mriQ_request 🚀
Pequeno sistema de "requests" (notificações com opção de aceitar/recusar) para FiveM, com UI NUI e utilitários para envio individual ou em grupo.
Principais recursos
- 📨 Envio de requests para jogadores (cliente/server).
- 🖼️ UI NUI com tempo, barra de progresso e extras customizáveis.
- 👥 Suporte para envio a múltiplos alvos e espera por respostas (export
sendAndWait). - ⚙️ Configuração central em
shared/config.lua. - 🔗 Dependência:
ox_lib.
Instalação rápida
- Copie a pasta
g5-requestpara a pasta de recursos do servidor. - Adicione
ensure g5-requestnoserver.cfg. - Tenha
ox_libinstalado e disponível no servidor.
Estrutura de arquivos 📁
g5-request/
├── client/ # scripts cliente
│ └── main.lua
├── server/ # scripts servidor
│ └── main.lua
├── shared/ # configurações compartilhadas
│ └── config.lua
├── html/ # UI NUI
│ ├── index.html
│ ├── script.js
│ └── style.css
├── fxmanifest.lua
└── README.mdConfiguração (shared/config.lua) ⚙️
Ajuste as seguintes opções conforme necessário:
Position:'top-right'|'top-left'— posição padrão da UI.DefaultTimeout: tempo padrão (ms) usado por operações de grupo/exportsendAndWait.AcceptKey/DenyKey: teclas padrão para aceitar/recusar (são usadas como fallback; o cliente registra keybinds via ox_lib e envia a tecla atual para a NUI na inicialização).
Exemplo:
Config = {
Position = 'top-right',
DefaultTimeout = 15000,
AcceptKey = 'Y',
DenyKey = 'N',
}Observação importante sobre timeouts:
- O export
sendAndWaitusaConfig.DefaultTimeoutcomo fallback se nenhum timeout for passado. - O envio direto via evento (
g5-request:server:send) aplica um timeout padrão de 15000 ms casorequestData.timeoutnão seja informado (vejaserver/main.lua).
Como enviar um request (server-side)
Utilize o evento para enviar um request a um jogador:
TriggerEvent('g5-request:server:send', targetServerId, requestData)Exemplo mínimo de requestData:
local request = {
title = 'Pedido',
titleIcon = 'user',
tag = 'INFO',
code = '1234',
extras = {
{ icon = 'info', name = 'Obs', value = 'Detalhes aqui' }
},
timeout = 15000, -- se omitido, envio único usa 15000ms por padrão no servidor
tagColor = '#FF0000',
progressColor = '#00FF00',
codeColor = '#FFFFFF',
sound = 'ping' -- opcional: nome do arquivo em html/assets/sound (sem extensão). Use 'off' para desativar.
}
TriggerEvent('g5-request:server:send', 2, request)Detalhes sobre o campo sound:
- Pode ser um nome sem extensão (ex:
"ding"). A NUI tentará carregar arquivos na pastahtml/assets/soundcom extensões comuns. - Se o nome já contiver extensão (ex:
alert.mp3), será usado tal qual emassets/sound/<nome>. - Use
"off"(string) para desativar som.
Envio a múltiplos alvos e espera por respostas (export)
Use o export para enviar requests a múltiplos alvos e aguardar respostas agregadas:
Server export:
local results = exports['g5-request']:sendAndWait(targetsTable, requestData, timeoutMs)Também é possível chamar via callback/await fornecido pelo sistema de callbacks (lib.callback):
local results = lib.callback.await('g5-request:sendAndWait', {2,3}, requestData, 20000)
for pid, res in pairs(results) do
print(pid, res.answered, res.accepted, res.timedOut, res.canceled, res.pending)
endFormato do retorno:
- Retorna uma tabela indexada por server id com objetos:
answered(boolean): se o jogador respondeu.accepted(boolean): se aceitou.timedOut(boolean): se expirou sem resposta.canceled(boolean): se o request foi cancelado.pending(boolean): se o request não foi enviado por ser considerado duplicado (já pendente) — nesse casoanswered=falseepending=true.
Internamente o servidor cria um groupId para correlacionar respostas e aguarda até timeoutMs (ou Config.DefaultTimeout) antes de devolver resultados.
Cancelamento de requests
Você pode cancelar um request individual ou um grupo:
- Evento server (qualquer script/server-side):
-- cancela request específico enviado ao player 2 com id "abcd1234"
TriggerEvent('g5-request:server:cancel', 2, 'abcd1234')
-- cancela um grupo pelo groupId (ex: "group:16409952001234")
TriggerEvent('g5-request:server:cancel', 'group:16409952001234')- Exports (server):
-- cancelamento individual (retorna boolean indicando sucesso)
local ok = exports['g5-request']:cancelRequest(targetServerId, requestId)
-- cancelamento de grupo (retorna boolean indicando sucesso)
local ok = exports['g5-request']:cancelGroup(groupId)- Comando de teste (server, requer
group.admin):
/cancelrequest <targetServerId> <requestId>Quando cancelado:
- O jogador alvo terá o request removido da UI (se estiver visível). Internamente o servidor envia o evento cliente
g5-request:client:removepara forçar a remoção local. - Se o request pertencer a um envio em grupo (
sendAndWait/ export), o resultado para aquele jogador terá o campocanceled = true.
Observação adicional sobre comportamento:
- Cancelar um grupo marca o grupo como cancelado e tenta remover todos os requests relacionados nas filas dos jogadores; os resultados do export
sendAndWaitpara esses alvos terãocanceled = true.
Status / Verificação de requests
Fornecemos APIs para checar o status de requests (úteis para scripts que devem auditar ou reagir a estados):
- Exports (server):
-- retorna tabela resumida ou informação específica
local info = exports['g5-request']:getRequestStatus(targetServerId, requestIdOrMatcher)
local group = exports['g5-request']:getGroupStatus(groupId)- Callbacks (pode usar lib.callback/await ou lib.callback):
local info = lib.callback.await('g5-request:getRequestStatus', targetServerId, requestIdOrMatcher)
local group = lib.callback.await('g5-request:getGroupStatus', groupId)Formato de retorno de getRequestStatus:
- Quando chamado só com target (segundo argumento nil): { found = boolean, queue = { {id, from, code, tag, timeout, groupId}, ... } }
- Quando chamado com id: { found = true, inQueue = true, request =
} ou { found = false }
Formato de retorno de getGroupStatus:
- { created =
, canceled = , results = { [targetId] = { answered, accepted, timedOut, canceled, pending }, ... }, pendingTargets = { [targetId] = true, ... } }
Callbacks / Eventos relevantes
- Evento para envio:
g5-request:server:send(server-side). - Callback server para respostas:
g5-request:answer(registrado vialib.callback.registerno servidor). Recebe (source, id, accepted) e retorna boolean indicando sucesso. - Export server:
sendAndWait(usapendingGroupRequestsinternamente para agregar respostas). - Eventos/notifications ao originador em casos de duplicata:
- Cliente originador recebe:
g5-request:server:duplicate_notify(targetId, requestData, existingRequestId) - Server-side pode capturar
g5-request:server:send:duplicatequando o envio for duplicado.
- Cliente originador recebe:
Comandos de teste (requer group.admin) 🧪
Para testar o envio de requests, utilize os seguintes comandos (implementados no servidor):
/sendtestrequest <target>— envia um request de teste paratarget(server id)./sendgrouptest <id1,id2,...>— envia para múltiplos alvos e aguarda respostas (usa export internamente).
NUI / comportamento do cliente
- A NUI recebe a tecla atual de aceitar/recusar (vinda do keybind registrado no cliente) ao inicializar via
initmessage. - A NUI toca sons conforme o campo
sound(veja regras acima). - Requests expiram automaticamente na NUI ao alcançar o timeout e então enviam resposta negativa ao servidor.
- A interface tenta calcular contraste de cores para texto automaticamente (caso sejam usados hex ou rgb).
Observações importantes ⚠️
- Requests expiram automaticamente após
timeoute são tratados como não respondidos se o usuário não responder (o servidor marcatimedOut = true). - O sistema usa filas por jogador no servidor; quando um jogador desconecta, sua fila é limpa.
- As IDs das requests são geradas automaticamente se não fornecidas.
- Para chamadas de grupo, se um jogador não responder antes do timeout, o resultado para ele terá
answered = false,accepted = falseetimedOut = true.
Contribuições e melhorias são bem-vindas — abra PRs ou issues. 🙌
Comportamento de duplicatas / request pendente
- Se você tentar enviar o "mesmo" request para um jogador duas vezes (mesma id, ou mesma combinação originador+code+tag), o servidor evita inserir duplicatas na fila.
- Para chamadas via export
sendAndWait, alvos que já possuíam o mesmo request receberão um resultado compending = true(eanswered=false), permitindo que o chamador saiba que o request já está pendente. - Quando um envio simples via evento for duplicado, o originador é notificado via client event
g5-request:server:duplicate_notifyou via evento serverg5-request:server:send:duplicatepara handlers server-side.
Prolongamento (prolong)
- Você pode solicitar que um request já pendente tenha seu timeout reiniciado usando o campo
prolongnorequestData. prolong = <number>— define o novo timeout em ms e REINICIA o timer para esse valor.prolong = trueerequestData.timeout = <number>— usarequestData.timeoutcomo novo timeout e REINICIA o timer.- Observação: o comportamento é de "reset" (redefinir o tempo restante), não "adicionar" ao tempo restante do request existente.
Exemplos práticos
- Envio simples (server-side)
local request = {
title = 'Pedido de Ajuda',
titleIcon = 'user',
tag = 'HELP',
code = 'A1',
extras = {
{ icon = 'map-marker', name = 'Local', value = 'Praça Central' },
{ icon = 'clock', name = 'Tempo', value = '30s' }
},
timeout = 15000,
sound = 'mixkit-doorbell-tone-2864'
}
TriggerEvent('g5-request:server:send', 2, request)- Envio para múltiplos alvos e espera por respostas (server-side, export)
local targets = {2, 3, 5}
local requestData = {
title = 'Votação Rápida',
tag = 'VOTE',
code = 'V123',
timeout = 20000
}
-- usando export
local results = exports['g5-request']:sendAndWait(targets, requestData, requestData.timeout)
for pid, res in pairs(results) do
print(('Player %s => answered=%s accepted=%s timedOut=%s pending=%s'):format(pid, tostring(res.answered), tostring(res.accepted), tostring(res.timedOut), tostring(res.pending)))
end- Uso com lib.callback.await (server-side)
local results = lib.callback.await('g5-request:sendAndWait', {2,3}, requestData, 20000)
-- processar results como no exemplo anterior- Cancelar um request específico (server-side)
-- cancela o request com id 'abcd' enviado para o player 2
TriggerEvent('g5-request:server:cancel', 2, 'abcd')- Consultar status de fila / grupo
local info = exports['g5-request']:getRequestStatus(2) -- lista a fila do jogador 2
local status = exports['g5-request']:getRequestStatus(2, 'abcd') -- procura request por id
local group = exports['g5-request']:getGroupStatus('16409952001234') -- retorna status do grupo