├── fxmanifest.lua ├── server ├── logs.settings.lua └── main.lua ├── LICENSE ├── readme.md ├── shared └── main.lua └── client └── main.lua /fxmanifest.lua: -------------------------------------------------------------------------------- 1 | fx_version 'cerulean' 2 | game 'gta5' 3 | 4 | name "Mycroft's Job Forms" 5 | description "FREE in-game Job Applications using ox lib" 6 | author "Mycroft" 7 | lua54 'yes' 8 | version "1.0.0" 9 | 10 | shared_scripts { 11 | '@ox_lib/init.lua', 12 | 'shared/*.lua' 13 | } 14 | 15 | client_scripts { 16 | 'client/*.lua' 17 | } 18 | 19 | server_scripts { 20 | 'server/logs.settings.lua', 21 | 'server/main.lua' 22 | } 23 | -------------------------------------------------------------------------------- /server/logs.settings.lua: -------------------------------------------------------------------------------- 1 | DiscordLogs = { 2 | Webhooks = { 3 | default = '', 4 | police = "", 5 | }, 6 | 7 | Colors = { -- https://www.spycolor.com/ 8 | default = 14423100, 9 | blue = 255, 10 | red = 16711680, 11 | green = 65280, 12 | white = 16777215, 13 | black = 0, 14 | orange = 16744192, 15 | yellow = 16776960, 16 | pink = 16761035, 17 | lightgreen = 65309 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | Copyright 2023 Mycroft (Kasey Fitton) 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 |

[Mycroft] Job Forms

Highly Configurable Job Forms with ox lib 2 | 3 | > This Script Was Made Because someone was selling a version of this with less features and Server Owners shouldn't have to spend money for such basic and simplistic code. 4 | 5 | > I've also decided to MIT license this code, do whatever you want with it. 6 | 7 | This Script is a fantastic way for players to apply for in-game jobs within their FiveM servers. This script allows players to fill out job applications in-game using a custom form that collects all the necessary information. Once the form is submitted, all the data is transferred via a webhook to a designated Discord channel, where it can 8 | be reviewed and processed by the server staff. 9 | 10 | Overall, the job application script for FiveM is a powerful and essential tool for servers that want to streamline their hiring process and provide a better user experience for job applicants. 11 | 12 | ## Features 13 | 14 | - Optional State-bag-based Cooldowns 15 | - Option to change `true/false` to `Yes/No` 16 | - Change **every** detail about the embed 17 | - Add As many questions as you want! 18 | - Supports loads of differant input types (text, number, checkbox, slider + more) 19 | - optional OX Target support 20 | - Completely customise EVERYTHING todo with the marker and target. 21 | - Input Santisation before data is sent to Discord 22 | - Cool embed formatting 23 | - Each Job can have their own webhook 24 | - Each Job can have their own Form 25 | 26 | ### Line Count 27 | 28 | - 335 (Including Config & fxmanifest & ReadMe) 29 | - 289 (Including Config & fxmanifest) 30 | - 266 (Including Config) 31 | - 157 (Excluding config/fxmanfiest/readme) 32 | 33 | ## Preview 34 | 35 | [![](https://i.imgur.com/5ej6Teu.png)](https://streamable.com/gex5u0) 36 | 37 | ## Notice 38 | 39 | Copyright 2023 Mycroft (Kasey Fitton) 40 | 41 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 42 | 43 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 44 | 45 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 46 | -------------------------------------------------------------------------------- /server/main.lua: -------------------------------------------------------------------------------- 1 | function Discord(name, title, color, fields) 2 | local webHook = DiscordLogs.Webhooks[name] or DiscordLogs.Webhooks.default 3 | local embedData = {{ 4 | ['title'] = title, 5 | ['color'] = DiscordLogs.Colors[color] or DiscordLogs.Colors.default, 6 | ['footer'] = { 7 | ['text'] = Config.ShowTime and Config.FooterText .. " " .. os.date() or Config.FooterText, 8 | ['icon_url'] = Config.FooterIconURL 9 | }, 10 | ['fields'] = fields, 11 | ['description'] = "", 12 | ['author'] = { 13 | ['name'] = Config.ServerName, 14 | ['icon_url'] = Config.AuthorAvatar 15 | } 16 | }} 17 | PerformHttpRequest(webHook, nil, 'POST', json.encode({ 18 | username = Config.BotUsername, 19 | avatar_url = Config.BotAvatar, 20 | embeds = embedData 21 | }), {['Content-Type'] = 'application/json'}) 22 | end 23 | 24 | function Sanitize(str) 25 | local replacements = { 26 | ['&' ] = '&', 27 | ['<' ] = '<', 28 | ['>' ] = '>', 29 | ['\n'] = '
' 30 | } 31 | 32 | return str 33 | :gsub('[&<>\n]', replacements) 34 | :gsub(' +', function(s) 35 | return ' '..(' '):rep(#s-1) 36 | end) 37 | end 38 | 39 | function TableToString(table) 40 | local string = "" 41 | for k,v in pairs(table) do 42 | local answer = Sanitize(tostring(v)) 43 | if not answer or answer == "" then answer = "N/A" end 44 | if Config.ChangeBoolsToStrings and answer == "true" then answer = "Yes" end 45 | if Config.ChangeBoolsToStrings and answer == "false" then answer = "No" end 46 | string = string .. (Config.MakeAnswersBold and ("**%s**: **%s** \n"):format(k, answer) or ("%s: %s"):format(k, answer)) 47 | end 48 | return string 49 | end 50 | 51 | function ConvertAnswers(Questions, Answers) 52 | for i=1, #(Answers) do 53 | if type(Answers[i]) == "table" then 54 | Questions[i].Answer = TableToString(Answers[i]) 55 | else 56 | local answer = Sanitize(tostring(Answers[i])) 57 | if not answer or answer == "" then answer = "N/A" end 58 | if Config.ChangeBoolsToStrings and answer == "true" then answer = "Yes" end 59 | if Config.ChangeBoolsToStrings and answer == "false" then answer = "No" end 60 | Questions[i].Answer = Config.MakeAnswersBold and ("**%s**"):format(answer) or answer 61 | end 62 | end 63 | return Questions 64 | end 65 | 66 | RegisterNetEvent("jobforms:apply", function(AreaIndex, Answers) 67 | local source = source 68 | if Player(source).state.ApplicationCooldown then 69 | return 70 | end 71 | local Ped = GetPlayerPed(source) 72 | local ped_pos = GetEntityCoords(Ped) 73 | local dist = #(Config.Areas[AreaIndex].Coords - ped_pos) 74 | if dist > 10.0 then 75 | return 76 | end 77 | local Questions = ConvertAnswers(Config.Areas[AreaIndex].Questions, Answers) 78 | local Fields = {} 79 | for i=1, #(Questions) do 80 | Fields[#Fields + 1] = {name = Questions[i].label, value = Questions[i].Answer, inline = false} 81 | end 82 | Discord(Config.Areas[AreaIndex].webhook, Config.Areas[AreaIndex].label, "green", Fields) 83 | Player(source).state:set("ApplicationCooldown", true, true) 84 | SetTimeout(Config.ApplicationSettings.Cooldown.time, function() 85 | Player(source).state:set("ApplicationCooldown", false, true) 86 | end) 87 | end) -------------------------------------------------------------------------------- /shared/main.lua: -------------------------------------------------------------------------------- 1 | Config = {} 2 | 3 | Config.UseTarget = false -- ox target support 4 | 5 | -- Discord Log Settings 6 | Config.ServerName = "Example Roleplay" 7 | Config.FooterText = "Mycroft | Job Forms | " 8 | Config.FooterIconURL = "" 9 | Config.ShowTime = true -- shows date in the footer 10 | Config.ChangeBoolsToStrings = true -- changes True/False to Yes/No 11 | Config.MakeAnswersBold = true 12 | Config.BotUsername = "Job Form Bot" 13 | Config.BotAvatar = "https://cdn.discordapp.com/emojis/939245183621558362.webp?size=128&quality=lossless" 14 | Config.AuthorAvatar = "https://cdn.discordapp.com/emojis/939245183621558362.webp?size=128&quality=lossless" 15 | 16 | --[[ 17 | Question Types: 18 | input, 19 | number, 20 | checkbox, 21 | select, 22 | slider 23 | color 24 | multi-select 25 | date 26 | date-range 27 | time 28 | textarea 29 | 30 | for more details, visit: https://overextended.github.io/docs/ox_lib/Interface/Client/input 31 | ]] 32 | 33 | -- Main Settings 34 | 35 | Config.Areas = { 36 | { 37 | label = "Police Application", 38 | webhook = "police", -- note if not present, it will send to "default" 39 | Coords = vector3(441.5298, -981.1339, 30.6896), 40 | Blip = { 41 | enabled = false, 42 | sprite = 1, 43 | size = 0.7, 44 | colour = 1, 45 | }, 46 | TargetSettings = { -- For when using OX-target 47 | radius = 2, 48 | debug = false, 49 | icon = "fa-solid fa-list-check", 50 | label = "Police Applications" 51 | }, 52 | MarkerSettings = { -- if not using OX-Target 53 | DrawMarker = true, 54 | size = vec3(1, 1, 1), 55 | rotation = vec3(1, 1, 1), 56 | type = 21, 57 | Distance = 10.0, 58 | colour = {r = 50, g = 200, b = 50, a = 200}, 59 | TextUI = "[E] -> Police Applications" 60 | }, 61 | Questions = { 62 | { 63 | type = "input", 64 | label = "Question 1", 65 | description = "This is a Required Question", 66 | placeholder = "Answer", 67 | required = true 68 | }, 69 | { 70 | type = "input", 71 | label = "Question 2", 72 | description = "This is a Non-Required Question", 73 | placeholder = "Answer", 74 | required = false 75 | }, 76 | { 77 | type = "checkbox", 78 | label = "Do you like Chicken?", 79 | description = "Very Important Yes/No Question.", 80 | required = false 81 | }, 82 | { 83 | type = "slider", 84 | label = "How Much Do you like Noodles?", 85 | default = 5, 86 | min = 1, 87 | max = 10, 88 | step = 1, 89 | required = true 90 | }, 91 | { 92 | type = "textarea", 93 | label = "Is the Sky Blue?", 94 | description = "This is a Very Long Question", 95 | placeholder = "Who Knows?", 96 | max = 50, -- Max Lines 97 | required = true 98 | }, 99 | } 100 | } 101 | } 102 | 103 | Config.ApplicationSettings = { 104 | Cooldown = { 105 | enabled = true, 106 | time = 5 * 60000 -- time in milliseconds (default: 5 mins) 107 | }, 108 | } 109 | -------------------------------------------------------------------------------- /client/main.lua: -------------------------------------------------------------------------------- 1 | function DoApplication(AreaIndex) 2 | local area = Config.Areas[AreaIndex] 3 | local Questions = area.Questions 4 | local input = lib.inputDialog(area.label, Questions) 5 | if not input then return end 6 | lib.notify({ 7 | title = 'Job Application', 8 | description = 'Successfully Sent your Application.', 9 | type = 'success' 10 | }) 11 | TriggerServerEvent("jobforms:apply", AreaIndex, input) 12 | end 13 | 14 | CreateThread(function() 15 | for i=1, #(Config.Areas) do 16 | if Config.UseTarget then 17 | local area = Config.Areas[i] 18 | exports.ox_target:addSphereZone({ 19 | coords = area.Coords, 20 | radius = area.TargetSettings.radius, 21 | debug = area.TargetSettings.debug, 22 | options = { 23 | { 24 | name = area.label, 25 | icon = area.TargetSettings.icon, 26 | label = area.TargetSettings.label, 27 | onSelect = function() 28 | DoApplication(i) 29 | end, 30 | canInteract = function() 31 | if not Config.ApplicationSettings.Cooldown.enabled then 32 | return true 33 | end 34 | if not LocalPlayer.state.ApplicationCooldown then 35 | return true 36 | end 37 | return false 38 | end 39 | } 40 | } 41 | }) 42 | else 43 | local point = lib.points.new(Config.Areas[i].Coords, Config.Areas[i].MarkerSettings.Distance, { 44 | area = Config.Areas[i], DrawingTextUI = false, areaIndex = i 45 | }) 46 | function point:nearby() 47 | if self.area.MarkerSettings.DrawMarker then 48 | local m_set = self.area.MarkerSettings 49 | DrawMarker(m_set.type, self.coords.x, self.coords.y, self.coords.z, 0, 0, 0, m_set.rotation.x, m_set.rotation.y, m_set.rotation.z, m_set.size.x, m_set.size.y, m_set.size.z, m_set.colour.r, m_set.colour.g, m_set.colour.b, m_set.colour.a, false, true, 2, nil, nil, false) 50 | end 51 | 52 | if self.currentDistance < 1 then 53 | if not self.DrawingTextUI then 54 | self.DrawingTextUI = true 55 | lib.showTextUI(self.area.MarkerSettings.TextUI) 56 | end 57 | if IsControlJustPressed(0, 38) then 58 | if Config.ApplicationSettings.Cooldown.enabled then 59 | if LocalPlayer.state.ApplicationCooldown then 60 | return lib.notify({ 61 | title = 'Job Application', 62 | description = 'Please Wait for your Cooldown to finish.', 63 | icon = 'ban', 64 | iconColor = '#C53030' 65 | }) 66 | end 67 | end 68 | DoApplication(self.areaIndex) 69 | end 70 | else 71 | if self.DrawingTextUI then 72 | self.DrawingTextUI = false 73 | lib.hideTextUI() 74 | end 75 | end 76 | end 77 | end 78 | if Config.Areas[i].Blip.enabled then 79 | local blip_set = Config.Areas[i].Blip 80 | local blip = AddBlipForCoord(Config.Areas[i].Coords) 81 | SetBlipSprite (blip, blip_set.sprite) 82 | SetBlipAsShortRange(blip, true) 83 | SetBlipColour(blip, blip_set.colour) 84 | SetBlipScale (blip, blip_set.size) 85 | BeginTextCommandSetBlipName("STRING") 86 | AddTextComponentSubstringPlayerName(Config.Areas[i].label) 87 | EndTextCommandSetBlipName(blip) 88 | end 89 | end 90 | end) --------------------------------------------------------------------------------