├── .editorconfig ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── general-question.md └── pull_request_template.md ├── .gitignore ├── .vscode └── settings.json ├── LICENSE ├── client └── client.lua ├── config.js ├── docs ├── _config.yml ├── _layouts │ └── default.html ├── changelog.md ├── code_of_conduct.md ├── commands.md ├── config.md ├── contributing.md ├── exports.md ├── faq.md ├── images │ └── intents.png ├── index.md └── readme.md ├── fxmanifest.lua ├── locales ├── ar.js ├── bg.js ├── cs.js ├── de.js ├── en.js ├── es.js ├── fr.js ├── id.js ├── it.js ├── nl.js ├── no.js ├── pl.js ├── pt.js ├── sl.js ├── sv.js ├── tr.js └── vn.js ├── optional addons ├── liveplayercount.js └── timedmessage.js ├── package.json ├── readme.md ├── server ├── addons │ └── log.js ├── bot.js ├── commands │ ├── announcement.js │ ├── embed.js │ ├── identifiers.js │ ├── kick.js │ ├── kickall.js │ ├── kill.js │ ├── message.js │ ├── onlinecount.js │ ├── players.js │ ├── qb-ban.js │ ├── qb-clothingmenu.js │ ├── qb-gang.js │ ├── qb-inventory.js │ ├── qb-jail.js │ ├── qb-job.js │ ├── qb-logout.js │ ├── qb-money.js │ ├── qb-permissions.js │ ├── qb-revive.js │ ├── qb-reviveall.js │ ├── qb-time.js │ ├── qb-user-checkonline.js │ ├── qb-vehicle.js │ ├── qb-weather.js │ ├── resource.js │ ├── screenshot.js │ ├── server.js │ ├── teleport.js │ ├── teleportall.js │ └── whitelist.js ├── events │ ├── guildBanAdd.js │ ├── guildBanRemove.js │ ├── guildMemberAdd.js │ ├── guildMemberRemove.js │ ├── guildMemberUpdate.js │ ├── guildUpdate.js │ ├── interactionCreate.js │ ├── inviteCreate.js │ ├── inviteDelete.js │ ├── messageCreate.js │ ├── messageDelete.js │ └── ready.js ├── server.js └── utils.js └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | indent_style = space 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | indent_size = 4 -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: zfbx 4 | patreon: zfbx 5 | ko_fi: zfbx8 6 | custom: ['https://www.buymeacoffee.com/zfbx', 'https://paypal.me/zfbx'] 7 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG]" 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Information:** 27 | - FiveM Artifact #: [e.g. 3240] 28 | - Running QBCore?: [yes/no] 29 | - If running QBCore when did you last update it? [e.g. today, 1 week] 30 | - Have you checked for other issues reporting this bug?: [yes/no] 31 | 32 | **Additional context** 33 | Add any other context about the problem here. 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea or command for this project 4 | title: "[Feature]" 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/general-question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: General Question 3 | about: General support questions or issues 4 | title: '' 5 | labels: question 6 | assignees: '' 7 | 8 | --- 9 | 10 | Before submitting your question have you checked the check the [FAQ](https://github.com/zfbx/zdiscord/wiki) or checked for similar issues already?: [yes/no] 11 | 12 | **Question:** 13 | Describe your question.. 14 | 15 | Information: 16 | FiveM artifact #: [eg. 3200] 17 | Using QBCore?: [yes/no] 18 | When did you last update QBCore if you are using it?: [eg. yesterday, 1 week ago] 19 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | **Describe Pull request** 2 | First, make sure you've read and are following the contribution guidelines and style guide and your code reflects that. 3 | Write up a clear and concise description of what your pull request adds or fixes and if it's an added feature explain why you think it should be included in the core. 4 | 5 | If your PR is to fix an issue mention that issue here 6 | 7 | **Questions (please complete the following information):** 8 | - You have personally tested this feature in all of it's ability and tried to break it? [yes/no] (Be honest) 9 | - Does your PR fit the [contribution guidelines](https://zfbx.github.io/zdiscord/contributing)? [yes/no] 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .yarn.installed 3 | package-lock.json 4 | screenshots/* 5 | 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # Optional npm cache directory 48 | .npm 49 | 50 | # Optional eslint cache 51 | .eslintcache 52 | 53 | # Optional REPL history 54 | .node_repl_history 55 | 56 | # Output of 'npm pack' 57 | *.tgz 58 | 59 | # Yarn Integrity file 60 | .yarn-integrity 61 | 62 | # dotenv environment variables file 63 | .env 64 | test.js 65 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Lua.diagnostics.globals": [ 3 | "fx_version", 4 | "games", 5 | "author", 6 | "description", 7 | "repository", 8 | "version", 9 | "files", 10 | "server_scripts", 11 | "dependencies", 12 | "server_only", 13 | "license", 14 | "dependency", 15 | "server_script", 16 | "client_script", 17 | "PlayerPedId", 18 | "SetEntityHealth", 19 | "RegisterNetEvent", 20 | "lua54", 21 | "GetCurrentResourceName", 22 | "SetPedCoordsKeepVehicle", 23 | "SetEntityCoords" 24 | ], 25 | "cSpell.words": [ 26 | "alloc", 27 | "bannedby", 28 | "bantime", 29 | "charinfo", 30 | "citizenfx's", 31 | "citizenid", 32 | "commandname", 33 | "Convar", 34 | "convars", 35 | "criminalrecord", 36 | "Dabbzz", 37 | "Deutsch", 38 | "discordinvite", 39 | "EXTRASUNNY", 40 | "firstname", 41 | "fivem", 42 | "fxmanifest", 43 | "injail", 44 | "isdst", 45 | "kickall", 46 | "labkey", 47 | "lastname", 48 | "markedbills", 49 | "mazeroof", 50 | "militarybase", 51 | "moneytype", 52 | "multicharacter", 53 | "nextbtn", 54 | "onesync", 55 | "onlinecount", 56 | "oxmysql", 57 | "Perro", 58 | "playercount", 59 | "previousbtn", 60 | "printerdocument", 61 | "Prios", 62 | "qbcore", 63 | "qbus", 64 | "Radhwane", 65 | "removepermissions", 66 | "reviveall", 67 | "serverip", 68 | "servername", 69 | "SNOWLIGHT", 70 | "socio", 71 | "styleguide", 72 | "Styleguides", 73 | "teleportall", 74 | "trialmod", 75 | "Unjail", 76 | "userpresence", 77 | "wday", 78 | "weathersync", 79 | "xenfovn", 80 | "yday", 81 | "Zancudo", 82 | "zdiscord", 83 | "zfbx" 84 | ] 85 | } 86 | -------------------------------------------------------------------------------- /client/client.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | - This file is part of zdiscord. 3 | - Copyright (C) 2021 Tony/zfbx 4 | - source: 5 | - 6 | - This work is licensed under the Creative Commons 7 | - Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | - To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ 9 | - or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 10 | --]] 11 | 12 | RegisterNetEvent(GetCurrentResourceName()..':kill', function() 13 | SetEntityHealth(PlayerPedId(), 0) 14 | end) 15 | 16 | RegisterNetEvent(GetCurrentResourceName()..':teleport', function(x, y, z, withVehicle) 17 | x = tonumber(x) 18 | y = tonumber(y) 19 | z = tonumber(z) 20 | if (withVehicle) then 21 | SetPedCoordsKeepVehicle(PlayerPedId(), x, y, z) 22 | else 23 | SetEntityCoords(PlayerPedId(), x, y, z); 24 | end 25 | end) 26 | 27 | function serverOnly() 28 | print("[ERROR] The triggered event can only be run on the server.") 29 | end 30 | 31 | exports('isRolePresent', serverOnly) 32 | exports('getDiscordId', serverOnly) 33 | exports('getRoles', serverOnly) 34 | exports('getName', serverOnly) 35 | exports('log', serverOnly) 36 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | /* 2 | zdiscord - by Tony/zfbx - https://github.com/zfbx/zdiscord - License: CC BY-NC-SA 4.0 3 | Docs for this file available at https://zfbx.github.io/zdiscord/config or in docs/config.md 4 | */ 5 | 6 | 7 | /** ****************************** 8 | * GENERAL CONFIGURATION SETTINGS 9 | ********************************/ 10 | 11 | const LanguageLocaleCode = "en"; 12 | 13 | // PUBLIC VALUES 14 | const FiveMServerName = "My FiveM Server"; 15 | const DiscordInviteLink = "https://discord.gg/fivem"; 16 | const FiveMServerIP = "127.0.0.1"; 17 | 18 | // This spams the console, only enable for testing if needed 19 | const DebugLogs = false; 20 | 21 | 22 | /** ******************** 23 | * DISCORD BOT SETTINGS 24 | ***********************/ 25 | 26 | const EnableDiscordBot = true; 27 | 28 | // DISCORD BOT 29 | const DiscordBotToken = "CHANGE"; 30 | const DiscordGuildId = "000000000000000000"; 31 | 32 | // STAFF CHAT 33 | const EnableStaffChatForwarding = false; 34 | const DiscordStaffChannelId = "000000000000000000"; 35 | const AdditionalStaffChatRoleIds = [ 36 | // "000000000000000", 37 | ]; 38 | 39 | // WHITELISTING / ALLOWLISTING 40 | const EnableWhitelistChecking = true; 41 | const DiscordWhitelistRoleIds = "000000000000000000, 000000000000000000"; 42 | 43 | // SLASH COMMANDS / DISCORD PERMISSIONS 44 | const EnableDiscordSlashCommands = true; 45 | const DiscordModRoleId = "000000000000000000"; 46 | const DiscordAdminRoleId = "000000000000000000"; 47 | const DiscordGodRoleId = "000000000000000000"; 48 | 49 | // DISCORD BOT STATUS 50 | const EnableBotStatusMessages = true; 51 | const BotStatusMessages = [ 52 | "{servername}", 53 | "{playercount} online", 54 | ]; 55 | 56 | // ACE PERMISSIONS 57 | const EnableAutoAcePermissions = false; 58 | const AutoAcePermissions = { 59 | // "example": "000000000000000000", 60 | // "example2": [ "000000000000000000", "000000000000000000"], 61 | }; 62 | 63 | // Other 64 | const SaveScreenshotsToServer = false; 65 | 66 | 67 | /** ************************ 68 | * WEBHOOK LOGGING SETTINGS 69 | **************************/ 70 | 71 | const EnableLoggingWebhooks = false; 72 | const LoggingWebhookName = "zLogs"; 73 | // put "&" in front of the id if you're to ping a role 74 | const LoggingAlertPingId = "&000000000000000000"; 75 | // example: "bank": "https://discord.com/webhook/...", 76 | const LoggingWebhooks = { 77 | "example": "https://discord.com/api/webhooks/000000000/sEcRRet-ToK3n_5tUfF_tH8t_YUo-S40u1d-n07-sHar3", 78 | }; 79 | 80 | 81 | /** !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 82 | * !! DO NOT EDIT BELOW THIS LINE UNLESS YOU KNOW WHAT YOU'RE DOING !! 83 | * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/ 84 | 85 | module.exports = { 86 | EnableDiscordBot: getConBool("discord_enable_bot", EnableDiscordBot), 87 | EnableStaffChatForwarding: getConBool("discord_enable_staff_chat", EnableStaffChatForwarding), 88 | EnableLoggingWebhooks: getConBool("discord_enable_logging_webhooks", EnableLoggingWebhooks), 89 | DebugLogs: getConBool("discord_debug", DebugLogs), 90 | DiscordBotToken: GetConvar("discord_token", DiscordBotToken), 91 | DiscordGuildId: GetConvar("discord_guild_id", DiscordGuildId), 92 | LanguageLocaleCode: GetConvar("discord_lang", LanguageLocaleCode), 93 | FiveMServerName: GetConvar("discord_server_name", FiveMServerName), 94 | DiscordInviteLink: GetConvar("discord_invite", DiscordInviteLink), 95 | FiveMServerIP: GetConvar("discord_server_ip", FiveMServerIP), 96 | EnableWhitelistChecking: getConBool("discord_enable_whitelist", EnableWhitelistChecking), 97 | DiscordWhitelistRoleIds: getConList("discord_whitelist_roles", DiscordWhitelistRoleIds), 98 | EnableDiscordSlashCommands: getConBool("discord_enable_commands", EnableDiscordSlashCommands), 99 | DiscordModRoleId: GetConvar("discord_mod_role", DiscordModRoleId), 100 | DiscordAdminRoleId: GetConvar("discord_admin_role", DiscordAdminRoleId), 101 | DiscordGodRoleId: GetConvar("discord_god_role", DiscordGodRoleId), 102 | EnableBotStatusMessages: getConBool("discord_enable_status", EnableBotStatusMessages), 103 | BotStatusMessages: BotStatusMessages, 104 | EnableAutoAcePermissions: getConBool("discord_enable_ace_perms", EnableAutoAcePermissions), 105 | AutoAcePermissions: AutoAcePermissions, 106 | SaveScreenshotsToServer: getConBool("discord_save_screenshots", SaveScreenshotsToServer), 107 | DiscordStaffChannelId: GetConvar("discord_staff_channel_id", DiscordStaffChannelId), 108 | LoggingWebhooks: LoggingWebhooks, 109 | LoggingAlertPingId: GetConvar("discord_logging_ping_id", LoggingAlertPingId), 110 | LoggingWebhookName: GetConvar("discord_logging_name", LoggingWebhookName), 111 | StaffChatRoleIds: [ 112 | GetConvar("discord_mod_role", DiscordModRoleId), 113 | GetConvar("discord_admin_role", DiscordAdminRoleId), 114 | GetConvar("discord_god_role", DiscordGodRoleId), 115 | ...AdditionalStaffChatRoleIds, 116 | ], 117 | }; 118 | 119 | /** Returns convar or default value fixed to a true/false boolean 120 | * @param {boolean|string|number} con - Convar name 121 | * @param {boolean|string|number} def - Default fallback value 122 | * @returns {boolean} - parsed bool */ 123 | function getConBool(con, def) { 124 | if (typeof def == "boolean") def = def.toString(); 125 | const ret = GetConvar(con, def); 126 | if (typeof ret == "boolean") return ret; 127 | if (typeof ret == "string") return ["true", "on", "yes", "y", "1"].includes(ret.toLocaleLowerCase().trim()); 128 | if (typeof ret == "number") return ret > 0; 129 | return false; 130 | } 131 | 132 | /** returns array of items or default array provided 133 | * @param {string} con - string of comma separated values 134 | * @param {string|Array} def - string of comma separated values 135 | * @returns {object} - array of discord ids */ 136 | function getConList(con, def) { 137 | const ret = GetConvar(con, def); 138 | if (typeof ret == "string") return ret.replace(/[^0-9,]/g, "").replace(/(,$)/g, "").split(","); 139 | if (Array.isArray(ret)) return ret; 140 | if (!ret) return []; 141 | } 142 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | title: zdiscord 2 | 3 | remote_theme: zfbx/tactile 4 | 5 | plugins: 6 | - jekyll-remote-theme 7 | 8 | include: contributing.md -------------------------------------------------------------------------------- /docs/_layouts/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | {% seo %} 10 | {% include head-custom.html %} 11 | 12 | 13 | 14 |
15 |
16 |
17 |

zdiscord

18 |

Github | Download | FAQ | Commands | Config | Exports

19 |
20 |
21 |
22 | {{ content }} 23 |
24 | 25 | 29 |
30 |
31 | 32 | 33 | -------------------------------------------------------------------------------- /docs/changelog.md: -------------------------------------------------------------------------------- 1 | ## Change log 2 | 3 | **7.3.0 - Emergency fix for discord api [2022-04-28]** 4 | - Discord broke everything without warning this fixes that 5 | 6 | **7.2.1 - Fixes and support [2022-04-15]** 7 | - update links 8 | - added swedish from ayatollah 9 | - support message added <3 10 | - docs updates 11 | - few bug fixes 12 | 13 | 14 | **7.2.0 - Addon improvements [2022-01-16]** 15 | 16 | - patched some errors from previous version 17 | - added example events for hooking from discord 18 | - updated api usage to be stable with newest branch of discord.js 19 | - added config for logging debug messages from discord.js 20 | 21 | **7.1.0 - Addon improvements [2022-01-14]** 22 | 23 | - added Italian from anonymous 24 | - bug fixes 25 | - add Czech from Anostraca 26 | - add vehicle commands 27 | - updated oxmysql compatibility 28 | - add Norwegian from VikingTheDev 29 | - optional addon for live player count added 30 | - Updated dependencies removing outdated and improving stablility 31 | 32 | **7.0.3 - Fixes + New config for addl staffchat roles [2021-12-10]** 33 | 34 | - fixed a few logging typos 35 | - added new config `AdditionalStaffChatRoleIds` (read config docs for more info) 36 | 37 | **7.0.2 - New languages and hot fixes [2021-12-9]** 38 | 39 | - added Bulgarian from @WrecksBG 40 | - added Spanish from @Xect0r 41 | - Fix /message error 42 | 43 | **7.0.1 - New language and QoL [2021-12-8]** 44 | 45 | - added French from @tiweb442 46 | - add sendMessage chat util 47 | 48 | **7.0.0 - Staff chat, new config AND WEBHOOK LOGGING [2021-12-8]** 49 | 50 | - added bi-directional staff chat (messages from discord staff channel <-> fiveM staff chat messages) 51 | - add discord webhook logging export (think qb-logs) 52 | - Refactored and added to config to be more clear 53 | - dutch added. Thanks Chatty <3 54 | - Slovenian added. Thanks Synthetics <3 55 | - remove command versioning 56 | - converted bot into class 57 | - added addon support 58 | - relicensed under CC-BY-SA-NC-4.0 59 | 60 | **6.0.0 - Refactor! [2021-10-25]** 61 | 62 | - command arguments have been refactored to pass as an object instead of an array (This is not backwards compatible which is why it's a version bump) 63 | 64 | **5.0.2 - New commands and docs! [2021-10-23]** 65 | 66 | - added screenshot command 67 | - added weather, blackout, time commands 68 | - added docs at [zfbx.github.io/zdiscord](https://zfbx.github.io/zdiscord) 69 | - updated/fixed chat events and teleport command 70 | 71 | **5.0.1 - Exports and ace perms [2021-10-20]** 72 | 73 | - Automatic Ace permission granting by discord role 74 | - new and changed exports for checking if role exist and getting roles 75 | 76 | **5.0.0 - Hello discord.js! & Much much more. [2021-10-18]** 77 | 78 | *It should be noted, this is essentially a complete rewrite of the resource and should probably be treated as such.* 79 | 80 | - NEW LICENSE - GPL version 3.0 81 | - Replaced eris with discord.js 82 | - replaced commands with slash commands 83 | - added a bunch of new commands 84 | - added a utils file to cleanup the main script 85 | - added client.js for running events client side 86 | - added linting and editorconfig 87 | 88 | **4.0.0 - permissions! bye yarn!.. and esx. [2021-9-15]** 89 | 90 | - Dependencies are now embedded libs making the project slimmer on install and not requiring yarn. 91 | - ESX support was dropped, it wasn't getting used and I don't have an esx server for testing. 92 | - translations were slimmed down to fivem messages only. 93 | - `add/removepermissions` merged into `perms` command 94 | - Convars changed to be more readable using standard FiveM format with `_` spacing 95 | 96 | **3.2.0 - Convars everywhere! [2021-7-24]** 97 | 98 | - Convar hooks added for nearly every config option [Read More](https://github.com/zfbx/zdiscord/wiki/Convars) 99 | - Slimmed up locales by removing console log messages 100 | - Added {prefix} to the global variables 101 | - New Add/Remove permissions command for QBCore 102 | 103 | 104 | **3.1.0 - QBCore (potential ESX) support added [2021-7-10]** 105 | 106 | - Commands starting with `qb-` will load automatically if QBCore is detected. 107 | - Placeholders for `esx-` commands have been added. 108 | 109 | 110 | **3.0.0 - Modular commands! [2021-7-10]** 111 | 112 | - Commands are now loaded dynamically from the `/commands` folder 113 | - Help command now has sub commands `!help commandName` 114 | - New DM command 115 | - various Error checks and fixes 116 | 117 | 118 | **2.0.0 - Github Release [2021-7-9]** 119 | 120 | - polished standalone 121 | - Added translation support 122 | 123 | 124 | **1.0.0 - First unsupported build [2021-6-10]** 125 | 126 | - A lot. 127 | -------------------------------------------------------------------------------- /docs/code_of_conduct.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributor Covenant Code of Conduct 3 | 4 | ## Our Pledge 5 | 6 | We as members, contributors, and leaders pledge to make participation in our 7 | community a harassment-free experience for everyone, regardless of age, body 8 | size, visible or invisible disability, ethnicity, sex characteristics, gender 9 | identity and expression, level of experience, education, socio-economic status, 10 | nationality, personal appearance, race, caste, color, religion, or sexual 11 | identity and orientation. 12 | 13 | We pledge to act and interact in ways that contribute to an open, welcoming, 14 | diverse, inclusive, and healthy community. 15 | 16 | ## Our Standards 17 | 18 | Examples of behavior that contributes to a positive environment for our 19 | community include: 20 | 21 | * Demonstrating empathy and kindness toward other people 22 | * Being respectful of differing opinions, viewpoints, and experiences 23 | * Giving and gracefully accepting constructive feedback 24 | * Accepting responsibility and apologizing to those affected by our mistakes, 25 | and learning from the experience 26 | * Focusing on what is best not just for us as individuals, but for the overall 27 | community 28 | 29 | Examples of unacceptable behavior include: 30 | 31 | * The use of sexualized language or imagery, and sexual attention or advances of 32 | any kind 33 | * Trolling, insulting or derogatory comments, and personal or political attacks 34 | * Public or private harassment 35 | * Publishing others' private information, such as a physical or email address, 36 | without their explicit permission 37 | * Other conduct which could reasonably be considered inappropriate in a 38 | professional setting 39 | 40 | ## Enforcement Responsibilities 41 | 42 | Community leaders are responsible for clarifying and enforcing our standards of 43 | acceptable behavior and will take appropriate and fair corrective action in 44 | response to any behavior that they deem inappropriate, threatening, offensive, 45 | or harmful. 46 | 47 | Community leaders have the right and responsibility to remove, edit, or reject 48 | comments, commits, code, wiki edits, issues, and other contributions that are 49 | not aligned to this Code of Conduct, and will communicate reasons for moderation 50 | decisions when appropriate. 51 | 52 | ## Scope 53 | 54 | This Code of Conduct applies within all community spaces, and also applies when 55 | an individual is officially representing the community in public spaces. 56 | Examples of representing our community include using an official e-mail address, 57 | posting via an official social media account, or acting as an appointed 58 | representative at an online or offline event. 59 | 60 | ## Enforcement 61 | 62 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 63 | reported to the community leaders responsible for enforcement on 64 | [Discord](discord). 65 | All complaints will be reviewed and investigated promptly and fairly. 66 | 67 | All community leaders are obligated to respect the privacy and security of the 68 | reporter of any incident. 69 | 70 | ## Enforcement Guidelines 71 | 72 | Community leaders will follow these Community Impact Guidelines in determining 73 | the consequences for any action they deem in violation of this Code of Conduct: 74 | 75 | ### 1. Correction 76 | 77 | **Community Impact**: Use of inappropriate language or other behavior deemed 78 | unprofessional or unwelcome in the community. 79 | 80 | **Consequence**: A private, written warning from community leaders, providing 81 | clarity around the nature of the violation and an explanation of why the 82 | behavior was inappropriate. A public apology may be requested. 83 | 84 | ### 2. Warning 85 | 86 | **Community Impact**: A violation through a single incident or series of 87 | actions. 88 | 89 | **Consequence**: A warning with consequences for continued behavior. No 90 | interaction with the people involved, including unsolicited interaction with 91 | those enforcing the Code of Conduct, for a specified period of time. This 92 | includes avoiding interactions in community spaces as well as external channels 93 | like social media. Violating these terms may lead to a temporary or permanent 94 | ban. 95 | 96 | ### 3. Temporary Ban 97 | 98 | **Community Impact**: A serious violation of community standards, including 99 | sustained inappropriate behavior. 100 | 101 | **Consequence**: A temporary ban from any sort of interaction or public 102 | communication with the community for a specified period of time. No public or 103 | private interaction with the people involved, including unsolicited interaction 104 | with those enforcing the Code of Conduct, is allowed during this period. 105 | Violating these terms may lead to a permanent ban. 106 | 107 | ### 4. Permanent Ban 108 | 109 | **Community Impact**: Demonstrating a pattern of violation of community 110 | standards, including sustained inappropriate behavior, harassment of an 111 | individual, or aggression toward or disparagement of classes of individuals. 112 | 113 | **Consequence**: A permanent ban from any sort of public interaction within the 114 | community. 115 | 116 | ## Attribution 117 | 118 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 119 | version 2.1, available at 120 | [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. 121 | 122 | Community Impact Guidelines were inspired by 123 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. 124 | 125 | For answers to common questions about this code of conduct, see the FAQ at 126 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at 127 | [https://www.contributor-covenant.org/translations][translations]. 128 | 129 | [discord]: https://discord.gg/M6neBU3cvP 130 | [homepage]: https://www.contributor-covenant.org 131 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html 132 | [Mozilla CoC]: https://github.com/mozilla/diversity 133 | [FAQ]: https://www.contributor-covenant.org/faq 134 | [translations]: https://www.contributor-covenant.org/translations 135 | -------------------------------------------------------------------------------- /docs/commands.md: -------------------------------------------------------------------------------- 1 | # Commands 2 | 3 | zdiscord comes preloaded with a fairly sizable set of default command with their own permission levels 4 | 5 | #### Table of Contents: 6 | 7 | - [Included Commands](#included-commands) 8 | - [Change permissions](#change-permissions) 9 | - [Add commands](#add-commands) 10 | - [Add permission levels](#add-permission-levels) 11 | 12 | ### Included Commands (52) 13 | Key: *[required] (optional)* 14 | 15 | **Standalone Commands:** (25) 16 | 17 | `/announcement [message]` - mod+
18 | `/embed complex [channel] [json]` - god
19 | `/embed simple [channel] [message] (title) (image) (thumbnail) (footer) (color)` - god
20 | `/identifiers [id]` - admin+
21 | `/kick [id] (message)` - mod+
22 | `/kickall [message]` - admin+
23 | `/kill [id]` - admin+
24 | `/message [id] [message]` - mod+
25 | `/onlinecount`
26 | `/players` - mod+
27 | `/resource ensure [resource]` - god
28 | `/resource inspect [resource]` - god
29 | `/resource list` - god
30 | `/resource refresh` - god
31 | `/resource start [resource]` - god
32 | `/resource stop [resource]` - god
33 | `/server`
34 | `/screenshot [id]` - god
35 | `/teleport coords [id] [x] [y] [z] (keepVehicle)` - mod+
36 | `/teleport preset [id] [location] (keepVehicle)` - mod+
37 | `/teleport-all coords [x] [y] [z]` - god
38 | `/teleport-all preset [location]` - god
39 | `/whitelist toggle [true/false]` - god
40 | `/whitelist addrole [role]` - god
41 | `/whitelist removerole [role]` - god
42 | 43 | **QBCore Commands:** (27) 44 | 45 | `/ban [id] [time] [reason]` - admin+
46 | `/clothing-menu [id]` - admin+
47 | `/gang kick [id]` - admin+
48 | `/gang inspect [id]` - admin+
49 | `/gang set [id] [gang] [grade]` - admin+
50 | `/inventory give [id] [item] [count]` - admin+
51 | `/inventory inspect [id]` - admin+
52 | `/inventory take [id] [item] [count]` - admin+
53 | `/jail free [id]` - mod+
54 | `/jail sentence [id] [time]` - mod+
55 | `/job fire [id]` - admin+
56 | `/job inspect [id]` - admin+
57 | `/job set [id] [job] [grade]` - admin+
58 | `/logout [id]` - admin+
59 | `/money add [id] [type] [amount]` - admin+
60 | `/money inspect [id]` - admin+
61 | `/money remove [id] [type] [amount]` - admin+
62 | `/money set [id] [type] [amount]` - admin+
63 | `/permissions add [id] [permission]` - god
64 | `/permissions remove [id]` - god
65 | `/revive [id]` - admin+
66 | `/revive-all` - god
67 | `/time [hour]` - admin+
68 | `/vehicle give [id] [spawncode] (plate)` - god
69 | `/vehicle lookup [plate]` - god
70 | `/weather blackout` - admin+
71 | `/weather set [weather]` - admin+
72 | 73 | ### Change permissions 74 | 75 | I tried to make all the permissions as fair and even as I could with my experience with servers and staffing, if you disagree with them it's quite easy to change.
76 | There's currently only 3 permission tiers (mod, admin, god). Permissions are granted by a hierarchy so "mod" will also mean "admin" and "god" can access them but a command with "god" will only be usable by someone with the "god" role. 77 | 78 | 1. Go to the `/commands/command-name.js` you want to modify and open it in a text/code editor. 79 | 2. Fine the line that looks like `role: "mod",` near the top (usually around line 26) 80 | 3. Change what is in the quotes `" "` to the permission you want to access the command. ie. `role: "admin",` 81 | 4. save and restart the resource or server. 82 | 83 | *Please note that permissions are synced across whole base commands so if you wanted `/money inspect` to be mod+ and `/money add` to be admin+ you'd need to separate them into separate commands to achieve that.* 84 | 85 | ### Add commands 86 | 87 | Adding commands can be really simple if you're familiar with javascript but very confusing otherwise. continue at your own risk and if you do make something cool, consider submitting a pull request and maybe it'll become a part of the default zdiscord commands :) 88 | 89 | 1. create a new javascript file under `/commands` with the name of your command (all lowercase, no spaces).
90 | *Note: If the command filename starts with `qb-` it will only load if QBCore is detected* 91 | 92 | 2. Paste in the following base. this is everything REQUIRED for a command to work properly: 93 | 94 | ```js 95 | module.exports = { 96 | name: "commandname", 97 | description: "description of command", 98 | 99 | run: async (client, interaction, args) => { 100 | return interaction.reply({ content: "The Message that will be sent to back when the command is run" }); 101 | }, 102 | }; 103 | ``` 104 | 3. If you wanted to make it so only admin or god could run your command you'd add the following 2 lines under your description line: 105 | 106 | ```js 107 | role: "admin", 108 | ``` 109 | 110 | 4. If you wanted to be able to accept input from the person submitting the command like an id and a message you'd add the following: 111 | 112 | ```js 113 | options: [ 114 | { 115 | name: "id", 116 | description: "Player's current id", 117 | required: true, 118 | type: "INTEGER", // This forces this value to be a number only 119 | }, 120 | { 121 | name: "message", 122 | description: "description for what this message is for", 123 | required: true, // Setting this to false will make it optional 124 | type: "STRING", 125 | }, 126 | ], 127 | 128 | run: async (client, interaction, args) => { // You should already have this from the first part 129 | // args will be named after the name in options (`args.id`, `args.message`) 130 | // Do what you want with id and message from here on 131 | ``` 132 | 133 | 5. If you need further guidance these links should help:
134 | [discord.js guide](https://discordjs.guide/#before-you-begin) - Great for beginners to discord.js
135 | [discord.js docs](https://discord.js.org/#/docs/main/stable/general/welcome) - Has almost everything you need to know
136 | [discord.com command docs](https://discord.com/developers/docs/interactions/application-commands) - specific details about how things work
137 | [FiveM server functions for JS](https://docs.fivem.net/docs/scripting-reference/runtimes/javascript/server-functions/) - Everything you can do with FiveM from a command
138 | **This resource itself** - Honestly there's so many features and practices being used actively here in commands and utils that you can take note from or in many cases just copy and paste and use so have at it and don't be afraid to break things to help learn more.. as long as you're not working on a live server while you're breaking things ;) 139 | 140 | 141 | -------------------------------------------------------------------------------- /docs/exports.md: -------------------------------------------------------------------------------- 1 | # Exports 2 | 3 | If you have other resources that you wish to be able to use information available in zdiscord, you might just be in luck. Here is a list of all the available exports for zdiscord and samples of how to use them in javascript or lua. If you don't see something you want here [submit a feature request](https://github.com/zfbx/zdiscord/issues/new/choose) or [pull request](https://github.com/zfbx/zdiscord/pulls) 4 | 5 | #### Table of Contents 6 | - [isRolePresent](#isrolepresent) 7 | - [getDiscordId](#getDiscordId) 8 | - [getRoles](#getroles) 9 | - [getName](#getname) 10 | - [log](#log) 11 | 12 | ### isRolePresent 13 | Returns a true/false boolean if a role is present for a role id or array of role-ids 14 | 15 | ```js 16 | // JAVASCRIPT EXAMPLE 17 | // Source - Discord Role ID 18 | const bool = global.exports.zdiscord.isRolePresent(global.source, "897991948097433681"); 19 | 20 | // Discord ID - Array of Roles 21 | const bool = global.exports.zdiscord.isRolePresent("142831624868855808", [ 22 | "897991948097433681", 23 | "897991948097433682" 24 | ]); 25 | ``` 26 | ```lua 27 | -- LUA EXAMPLE 28 | -- Source - Discord Role ID 29 | local bool = exports.zdiscord:isRolePresent(source, "897991948097433681"); 30 | 31 | -- Discord ID - Array of Roles 32 | local bool = exports.zdiscord:isRolePresent("142831624868855808", { 33 | "897991948097433681", 34 | "897991948097433682" 35 | }); 36 | ``` 37 | 38 | 39 | ### getDiscordId 40 | Returns a player's discord id 41 | 42 | ```js 43 | // JAVASCRIPT EXAMPLE 44 | const id = global.exports.zdiscord.getDiscordId(global.source); 45 | ``` 46 | ```lua 47 | -- LUA EXAMPLE 48 | local id = exports.zdiscord:getDiscordId(source); 49 | ``` 50 | 51 | 52 | 53 | ### getRoles 54 | Returns an array of roles for a discord id or source 55 | 56 | ```js 57 | // JAVASCRIPT EXAMPLE 58 | // Source 59 | const roles = global.exports.zdiscord.getRoles(global.source); 60 | 61 | // Discord ID 62 | const roles = global.exports.zdiscord.getRoles("142831624868855808"); 63 | ``` 64 | ```lua 65 | -- LUA EXAMPLE 66 | -- Source 67 | local roles = exports.zdiscord:getRoles(source); 68 | 69 | -- Discord ID 70 | local roles = exports.zdiscord:getRoles("142831624868855808"); 71 | ``` 72 | 73 | 74 | ### getName 75 | Returns an string containing the discord name/nickname for a discord id or source 76 | 77 | ```js 78 | // JAVASCRIPT EXAMPLE 79 | // Source 80 | const name = global.exports.zdiscord.getName(global.source); 81 | 82 | // Discord ID 83 | const name = global.exports.zdiscord.getName("142831624868855808"); 84 | ``` 85 | ```lua 86 | -- LUA EXAMPLE 87 | -- Source 88 | local name = exports.zdiscord:getName(source); 89 | 90 | -- Discord ID 91 | local name = exports.zdiscord:getName("142831624868855808"); 92 | ``` 93 | 94 | 95 | ### log 96 | send a message to a configured Log webhook 97 | 98 | ```js 99 | // JAVASCRIPT EXAMPLE 100 | // event, message, pingRole, color (optional) 101 | global.exports.zdiscord.log("modlog", "UserA Banned UserB for Reason", true, "#FF0000"); 102 | 103 | ``` 104 | ```lua 105 | -- LUA EXAMPLE 106 | -- event, message, pingRole, color (optional) 107 | exports.zdiscord:log("modlog", "UserA Banned UserB for Reason", true, "#FF0000"); 108 | ``` 109 | -------------------------------------------------------------------------------- /docs/faq.md: -------------------------------------------------------------------------------- 1 | # Frequently Asked Questions 2 | 3 | Here's a list of common problems people have run into and how to solve them. If you are having a problem that isn't listed here, check the [readme](https://github.com/zfbx/zdiscord/blob/djs/readme.md) and if there's still nothing submit an [issue](https://github.com/zfbx/zdiscord/issues) if it doesn't already exist there. 4 | 5 | #### Table of Contents: 6 | 7 | - [No Slash Commands?](#no-slash-commands) 8 | - [How to get a Discord id?](#how-to-get-a-discord-id) 9 | - ["ENABLE INTENTS" Error](#enable-intents-error) 10 | - [Can't find yarn dependency](#cant-find-yarn-dependency) 11 | - [QBCore commands not showing](#qbcore-commands-not-showing) 12 | - [Bot status message updates slow](#bot-status-message-updates-slow) 13 | - [Buffer Deprecation Warning](#buffer-deprecation-warning) 14 | - [Could not find dependency /server:4890](#could-not-find-dependency-server4890) 15 | - [QBCore commands aren't showing](#qbcore-commands-not-showing) 16 | - [New chat messages / announcements aren't popping up when recieved](#new-chat-messages--announcements-arent-popping-up-when-recieved) 17 | - [How to use custom chat](#how-to-use-custom-chat) 18 | 19 | 20 | ### No Slash Commands? 21 | 22 | Don't see slash commands when you type `/` in your server? 23 | 24 | - In your discord user settings (bottom left gear icon) > Text & Images > Text Box do you have "Use slash commands.." enabled? 25 | - Does the role trying to access the slash commands have the slash commands permission in discord roles? 26 | - Is your server's guild id set to your discord server? 27 | - Did you invite the bot to your server with the invite link provided (which includes `&scope=bot%20applications.commands` on the end) 28 | 29 | 30 | 31 | ### How to get a Discord id? 32 | 33 | Discord has a [guide](https://support.discord.com/hc/en-us/articles/206346498-Where-can-I-find-my-User-Server-Message-ID-) on how to do this but the TL;DR is enable "Developer Mode" in your discord settings `Settings (gear in bottom left) > Advanced > Developer Mode` then right click any of the **[usernames, role names, channel names, server icon]** and `Copy ID`. You now should be able to paste the ~20 digit id. 34 | 35 | 36 | ### "ENABLE INTENTS" Error 37 | 38 | zdiscord **needs** `Presence Intent` and `Server Members Intent` from the discord bot [settings](https://discord.com/developers/applications) to be enabled for basic functionality. Without these the bot is unable to check if a user is in your discord server or if they have the required roles to join the server if using the whitelist feature. 39 | 40 | Under the "Bot" section of the discord settings towards the bottom you'll see 2 toggles under the "Privileged Gateway Intents" section, They both need to be enabled like show below: 41 | ![intents](/zdiscord/images/intents.png) 42 | 43 | 44 | ### Can't find yarn dependency 45 | 46 | This resource requires FiveM's yarn module from [cfx-server-data](https://github.com/citizenfx/cfx-server-data) under `(resources/[system]/[builders]/yarn)` so either add those resources to your resource folder or just put the yarn one in your resources and zdiscord will enable it for itself so you don't need to include it in your cfg. 47 | 48 | **Note:** If you want to use the /screenshot command which uses [screenshot-basic](https://github.com/citizenfx/screenshot-basic) you will also need the webpack resource also under `[builders]`. So consider having that one ready too if you end up just copying the single resource. 49 | 50 | 51 | ### QBCore commands not showing 52 | 53 | For QBCore support you either have to `ensure qb-core` before this resource or have qb-core as a dependency in the fxmanifest.lua which will essentially start qb-core automatically before it but the former suggestion is more recommended since some dependency requirements can act up. 54 | 55 | ### QBCore commands aren't loading 56 | 57 | If you're using an older version of QBCore that uses the old `QBCore:GetObject` event you will have to update the server.js replacing lines ~20-23 which look like: 58 | ```js 59 | try { 60 | z.QBCore = global.exports["qb-core"].GetCoreObject(); 61 | if (z.QBCore) z.utils.log.info("QBCore found! Supported QB commands will be loaded."); 62 | } catch { z.QBCore = false; } 63 | ``` 64 | With this: 65 | ```js 66 | z.QBCore = false; 67 | TriggerEvent("QBCore:GetObject", (obj) => { z.QBCore = obj; }); 68 | if (z.QBCore) z.utils.log.info("QBCore found! Supported QB commands will be loaded."); 69 | ``` 70 | 71 | **Note:** Not all commands are backwards compatible with the previous version of QBCore as it would be very hard to support many versions of backwards compatibility so I recommend updating to a more recent version of qbcore :) *(Plus if you're using qbus it's probably a leak and you shouldn't be anyways)* 72 | 73 | 74 | ### Bot status message updates slow 75 | 76 | By default the bot status updates every 30 seconds to prevent abusing the discord API, you can lower this to probably 10 seconds at the lowest without problems by changing the last line in `/events/ready.js` where it's `30000` to `10000` (it's in milliseconds) 77 | 78 | 79 | ### Buffer Deprecation Warning 80 | 81 | **This was patched in version 7.0.0 of zdiscord and FiveM artifact 4980+. Update to these or later to make this go away.** 82 | 83 | You may see a warning like: 84 | ``` 85 | [DEP0005] DeprecationWarning: Buffer() is deprecated due to security and usability issues. Please use the Buffer.alloc(), Buffer.allocUnsafe(), or Buffer.from() methods instead. 86 | ``` 87 | 88 | This is fine.. it's just a warning that future versions may drop support for it. I don't see FiveM updating node again for quite some time so it's nothing to concern yourself with at the moment. This issue comes from a sub-dependency (node-fetch and whatwg) thinking FiveM is a browser so falling back to use `Buffer()`. I hope future updates of those resources will fix it, currently there's nothing I can do about it without forking entire collections of node modules and changing them. And I don't want the responsibility of keeping them updated especially when they're fairly active repositories. 89 | 90 | 91 | ### Could not find dependency /server:4890 92 | 93 | When trying to start zdiscord you get an error that says `Could not find dependency /server:4890 for resource zdiscord`. This means you aren't running [FiveM artifacts](https://runtime.fivem.net/artifacts/fivem/build_server_windows/master/) version 4890 or later. updating to a version number higher than 4890 will fix this error and allow the resource to start. 94 | 95 | 96 | ### New chat messages / announcements aren't popping up when recieved 97 | 98 | cfx chat by default has modes you can toggle between with a keybind (default: L) and if you change it to "WHEN ACTIVE" it'll show when new messages arrive 99 | 100 | ### How to use custom chat 101 | 102 | You really shouldn't use a custom chat but rather theme the default but IF you did.. you can change the functionality of the chat in zdiscord by modifying the function `chatMessage` in `utils.js` as all messages in game are forwarded through therre 103 | -------------------------------------------------------------------------------- /docs/images/intents.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfbx/zdiscord/3f6afd16d112fcadcfbcbc3d4991cb31d9374e33/docs/images/intents.png -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | A Discord bot that runs in FiveM for the purpose of whitelisting, moderation and utilities using [discord.js](https://discord.js.org/). The goal is for this this resource to be easy to setup and expand upon while giving your staff team an easy method of support and moderation of players in game without actually launching FiveM. This resource also heavily support [QBCore](https://github.com/qbcore-framework) in most of it's functionality but it's not required 2 | 3 | ## Features 4 | 5 | - Standalone FiveM resource (no external hosting required) 6 | - Uses [Slash commands](https://support.discord.com/hc/en-us/articles/1500000368501-Slash-Commands-FAQ) with help/suggestions 7 | - Moderation tools (kick, ban, inspect, etc) 8 | - [QBCore](https://github.com/qbcore-framework) [commands](https://zfbx.github.io/zdiscord/commands) included! 9 | - Easy to expand and customize with modular [commands](https://zfbx.github.io/zdiscord/commands#add-commands)! 10 | - Can be configured with [convars](https://zfbx.github.io/zdiscord/convars) 11 | - Automatic Ace Permission granting system 12 | - [Helpful exports](https://zfbx.github.io/zdiscord/exports) 13 | 14 | ## Supported Languages 15 | 16 | - en - English - By zfbx 17 | - de - Deutsch / German - By Anonymous 18 | - pl - Polish - By insaneArian 19 | - vn - Vietnamese - By xenfovn 20 | - ar - Arabic - By RadhwaneDZ 21 | - tr - Turkish - By alp1x 22 | - nl - Dutch - By ChatDisabled 23 | - sl - Slovenian - By Synthethics 24 | - es - Spanish - by Xect0r 25 | - bg - Bulgarian - By WrecksBG 26 | - it - Italian - By Anonymous 27 | - cs - Czech - by Anostraca 28 | - no - Norwegian - by VikingTheDev 29 | - sv - Swedish - by ayabolli 30 | - id - Indonesian - by xPutraGm 31 | 32 | ## License 33 | 34 | **Note: as of version 7.0.0 zdiscord, it is licensed under CC-BY-NC-SA-4.0** 35 | 36 |

zdiscord © 2021 by Tony (zfbx) is licensed under Attribution-NonCommercial-ShareAlike 4.0 International

37 | 38 | **TL;DR** 39 | - BY: Credit must be given to me, the creator. (Tony/zfbx) 40 | - NC: Only noncommercial use of your work is permitted. (You can use in your own FiveM server which may make money itself BUT can't in any way sell zdiscord itself in any way for any commercial advantage or monetary compensation) 41 | - SA: Adaptations must be shared under the same terms. 42 | 43 | -------------------------------------------------------------------------------- /docs/readme.md: -------------------------------------------------------------------------------- 1 | # View these docs from here -> [zdiscord docs](https://zfbx.github.io/zdiscord) 2 | -------------------------------------------------------------------------------- /fxmanifest.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | - This file is part of zdiscord. 3 | - Copyright (C) 2021 Tony/zfbx 4 | - source: 5 | - 6 | - This work is licensed under the Creative Commons 7 | - Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | - To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ 9 | - or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 10 | --]] 11 | 12 | fx_version "cerulean" 13 | games { "gta5" } 14 | 15 | author "zfbx" 16 | description "Discord bot allowlist and more" 17 | repository "https://github.com/zfbx/zdiscord" 18 | version "7.3.0" 19 | license "CC-BY-NC-SA-4.0" 20 | lua54 'yes' 21 | 22 | server_script "server/server.js" 23 | client_script "client/client.lua" 24 | 25 | dependencies { 26 | '/server:4890', -- Node16+ 27 | 'yarn', 28 | } 29 | -------------------------------------------------------------------------------- /locales/ar.js: -------------------------------------------------------------------------------- 1 | /* ar - ARABIC Translation by RADHWANEDZ 2 | Translation strings used by the bot 3 | Strings wrapped in { } will be auto-replaced with updated values 4 | Globals: {servername}, {invite}, (all from config) and {playercount} (current connected players) 5 | */ 6 | const locale = { 7 | checkingWhitelist: "يتم الان التحقق من حسابك اذا كان مفعل ام لا ،{name} مرحبا", 8 | discordNotOpen: "يجب ان تقوم بفتح تطبيق الديسكورد قبل فتح برنامج فايف ام ", 9 | fatalError: "نعتقد ان هناك خطأ ما في التحقق من تفعيل حسابك يرجى مراسلتنا عبر الديسكورد لحل المشكلة", 10 | notInDiscordServer: " {invite} يرجى الانضمام الي الديسكورد وتفعيل عضويتك، اليك الرابط {servername} انت لست عضوا في سيرفر الديسكورد الخاص بنا ", 11 | notWhitelisted: "ليتم تفعيل حسابك لتستطيع الدخول للمدينة {servername} يجب اجتياز المقابلة في الديسكورد اولا ", 12 | kickedWithoutReason: "لقد تم طردك من قبل المراقب.", 13 | announcement: "اعلان", 14 | directMessage: "الادارة", 15 | staffchat: "شات الادارة", 16 | }; 17 | 18 | module.exports = locale; 19 | -------------------------------------------------------------------------------- /locales/bg.js: -------------------------------------------------------------------------------- 1 | /* BG - Bulgarian Translation by Wrecks ¦ 2 | Translation strings used by the bot 3 | Strings wrapped in { } will be auto-replaced with updated values 4 | Globals: {servername}, {invite}, (all from config) and {playercount} (current connected players) 5 | */ 6 | const locale = { 7 | // Includes {name} + Globals 8 | checkingWhitelist: "Здравейте {name}, Проверяваме Вашият Whitelist статус..", 9 | discordNotOpen: "Трябва да имате отворен Discord преди да отворите FiveM (Рестартирайте и двете ако имате проблем)", 10 | fatalError: "Има проблем с получаването на Вашия Whitelist статус, моля опитайте отново или се свържете със съпорта в дискорд канала ако проблема продължава.", 11 | notInDiscordServer: "Вие не сте в {servername} Discord, Ако желаете можете да влезе от тук: {invite}", 12 | notWhitelisted: "Нямате определена роля в {servername} discord за да можете да влезете в сървъра, сигурни ли сте че имате Whitelist за този сървър?", 13 | kickedWithoutReason: "Бяхте кикнати от стафа.", 14 | announcement: "ОПОВЕСТЯВАНЕ", 15 | directMessage: "STAFF", 16 | staffchat: "STAFFCHAT", 17 | }; 18 | 19 | module.exports = locale; 20 | -------------------------------------------------------------------------------- /locales/cs.js: -------------------------------------------------------------------------------- 1 | /* cs - Czech Translation by Anostraca 2 | Translation strings used by the bot 3 | Strings wrapped in { } will be auto-replaced with updated values 4 | Globals: {servername}, {invite}, (all from config) and {playercount} (current connected players) 5 | */ 6 | const locale = { 7 | // Includes {name} + Globals 8 | checkingWhitelist: "Ahoj {name}, Kontrolujeme jestli máš povolený přístup..", 9 | discordNotOpen: "Musíš mít otevřený discord předtím, než zapneš FiveM (Pokud problém přetrvává tak restartuj obojí.)", 10 | fatalError: "Něco se pokazilo při ověřování jestli máš přístup, prosím opakuj akci později, nebo můžeš kontaktovat někoho z podpory na discordu.", 11 | notInDiscordServer: "Nejsi na {servername} Discordu, prosím připoj se sem: {invite}", 12 | notWhitelisted: "Nemáš {servername} roli na discordu která je požadovaná pro připojení na server. Máš vůbec povolený přístup?", 13 | kickedWithoutReason: "Byl jsi vyhozen Moderátorem.", 14 | announcement: "OZNÁMENÍ", 15 | directMessage: "MODERÁTOR", 16 | staffchat: "MODERAČNÍCHAT", 17 | }; 18 | 19 | module.exports = locale; 20 | -------------------------------------------------------------------------------- /locales/de.js: -------------------------------------------------------------------------------- 1 | /* de - German Translation by Anonymous 2 | Translation strings used by the bot 3 | Strings wrapped in { } will be auto-replaced with updated values 4 | Globals: {servername}, {invite}, (all from config) and {playercount} (current connected players) 5 | */ 6 | const locale = { 7 | checkingWhitelist: "Hi {name}, wir überprüfen deinen Whitelist status..", 8 | discordNotOpen: "Du musst Discord offen haben, bevor du FiveM startest..(Starte beide neu wenn ein Problem auftritt)", 9 | fatalError: "Beim Abrufen Ihres Whitelist-Status ist ein Fehler aufgetreten. Bitte versuchen Sie es in Kürze erneut oder kontaktieren Sie den Support im Discord, wenn das Problem weiterhin besteht.", 10 | notInDiscordServer: "Du bist nicht in dem {servername} Discord, tritt hier bei: {invite}", 11 | notWhitelisted: "Du hast nicht die {servername} Discord-Rolle, die benötigt wird um dem Server beizutreten, bist du auf der Whitelist?", 12 | kickedWithoutReason: "Du wurdest vom Team gekickt.", 13 | announcement: "ANNOUNCEMENT", 14 | directMessage: "STAFF", 15 | staffchat: "STAFFCHAT", 16 | }; 17 | 18 | module.exports = locale; 19 | -------------------------------------------------------------------------------- /locales/en.js: -------------------------------------------------------------------------------- 1 | /* en - English Translation by zfbx ♥ 2 | Translation strings used by the bot 3 | Strings wrapped in { } will be auto-replaced with updated values 4 | Globals: {servername}, {invite}, (all from config) and {playercount} (current connected players) 5 | */ 6 | const locale = { 7 | // Includes {name} + Globals 8 | checkingWhitelist: "Hi {name}, We're Checking your whitelist status..", 9 | discordNotOpen: "You must have discord open before starting FiveM (Restart both if problem persists)", 10 | fatalError: "Something went wrong fetching your whitelist status, please try again in a bit or contact support in the discord if problem persists.", 11 | notInDiscordServer: "You are not in the {servername} Discord, please join here: {invite}", 12 | notWhitelisted: "You do not have the {servername} discord role required to join this server, are you whitelisted?", 13 | kickedWithoutReason: "You've been kicked by staff.", 14 | announcement: "ANNOUNCEMENT", 15 | directMessage: "STAFF", 16 | staffchat: "STAFFCHAT", 17 | }; 18 | 19 | module.exports = locale; 20 | -------------------------------------------------------------------------------- /locales/es.js: -------------------------------------------------------------------------------- 1 | /* es - Spanish Translation by Xect0r 2 | Translation strings used by the bot 3 | Strings wrapped in { } will be auto-replaced with updated values 4 | Globals: {servername}, {invite}, (all from config) and {playercount} (current connected players) 5 | */ 6 | const locale = { 7 | // Includes {name} + Globals 8 | checkingWhitelist: "Hola {name}, estamos verificando tu estado en la Whitelist..", 9 | discordNotOpen: "Tienes que abrir Discord antes de iniciar FiveM (Reinicia ambos si el problema persiste)", 10 | fatalError: "Ha habido un problema al consultar tu estado en la Whitelist, intenta más tarde o contacta a soporte en Discord.", 11 | notInDiscordServer: "No te encuentras en el Discord de {servername}, puedes unirte aquí: {invite}", 12 | notWhitelisted: "No tienes el rol requerido en {servername} para ingresar a este servidor, te encuentras en la whitelist?", 13 | kickedWithoutReason: "Has sido expulsado por el Staff.", 14 | announcement: "ANUNCIO", 15 | directMessage: "STAFF", 16 | staffchat: "STAFFCHAT", 17 | }; 18 | 19 | module.exports = locale; 20 | -------------------------------------------------------------------------------- /locales/fr.js: -------------------------------------------------------------------------------- 1 | /* fr - French Translation by tiweb442 2 | Translation strings used by the bot 3 | Strings wrapped in { } will be auto-replaced with updated values 4 | Globals: {servername}, {invite}, (all from config) and {playercount} (current connected players) 5 | */ 6 | const locale = { 7 | // Includes {name} + Globals 8 | checkingWhitelist: "Salut {name}, Nous vérifions votre statut Whitelist..", 9 | discordNotOpen: "Vous devez avoir discord ouvert avant de commencer fivem (Redémarrer les deux si le problème persiste)", 10 | fatalError: "Un problème est survenu lors de la récupération de votre statut Whitelist., Veuillez réessayer dans quelques instants ou contacter le support dans le discord si le problème persiste.", 11 | notInDiscordServer: "Vous n'êtes pas dans le {servername} Discord, s'il vous plaît rejoindre ici: {invite}", 12 | notWhitelisted: "Vous n'avez pas le rôle discord de {servername} requis pour rejoindre ce serveur, êtes-vous sur la Whitelist ?", 13 | kickedWithoutReason: "Vous avez été expulser par le personnel.", 14 | announcement: "ANNONCE", 15 | directMessage: "STAFF", 16 | staffchat: "STAFFCHAT", 17 | }; 18 | 19 | module.exports = locale; 20 | -------------------------------------------------------------------------------- /locales/id.js: -------------------------------------------------------------------------------- 1 | /* id - Indonesia Translation by xPutraGm 2 | Translation strings used by the bot 3 | Strings wrapped in { } will be auto-replaced with updated values 4 | Globals: {servername}, {invite}, (all from config) and {playercount} (current connected players) 5 | */ 6 | const locale = { 7 | // Includes {name} + Globals 8 | checkingWhitelist: "Hai {name}, Kami Sedang Memeriksa status daftar putih(whitelist) Anda..", 9 | discordNotOpen: "Anda harus membuka Discord sebelum memulai FiveM (Mulai ulang keduanya jika masalah berlanjut)", 10 | fatalError: "Ada yang salah saat mengambil status daftar putih(whitelist) Anda, silakan coba lagi sebentar atau hubungi dukungan di discord jika masalah berlanjut.", 11 | notInDiscordServer: "Anda tidak ada di {servername} Discord, silakan bergabung di sini: {invite}", 12 | notWhitelisted: "Anda tidak memiliki peran discord {servername} yang diperlukan untuk bergabung dengan server ini, apakah Anda masuk daftar putih(whitelist)?", 13 | kickedWithoutReason: "Anda telah ditendang oleh staff.", 14 | announcement: "PENGUMUMAN", 15 | directMessage: "STAFF", 16 | staffchat: "STAFFCHAT", 17 | }; 18 | 19 | module.exports = locale; 20 | -------------------------------------------------------------------------------- /locales/it.js: -------------------------------------------------------------------------------- 1 | /* it - Italian Translation by anonymous ♥ 2 | Translation strings used by the bot 3 | Strings wrapped in { } will be auto-replaced with updated values 4 | Globals: {servername}, {invite}, (all from config) and {playercount} (current connected players) 5 | */ 6 | const locale = { 7 | // Includes {name} + Globals 8 | checkingWhitelist: "Ciao {name}, Stiamo verificando se possiedi la whitelist..", 9 | discordNotOpen: "Devi aprire Discord prima di avviare FiveM (Riavvia entrambi se il problema persiste)", 10 | fatalError: "Qualcosa è andato storto mentre stavamo verificando se possiedi la whitelist, per favore riprova tra un po' o contatta il supporto nel nostro discord se il problema persiste.", 11 | notInDiscordServer: "Non sei nel Discord di {servername}, per favore entra: {invite}", 12 | notWhitelisted: "Non hai il ruolo nel discord di {servername} richiesto per entrare nel server, possiedi la whitelist?", 13 | kickedWithoutReason: "Sei stato cacciato dallo staff.", 14 | announcement: "ANNUNCIO", 15 | directMessage: "STAFF", 16 | staffchat: "STAFFCHAT", 17 | }; 18 | 19 | module.exports = locale; 20 | -------------------------------------------------------------------------------- /locales/nl.js: -------------------------------------------------------------------------------- 1 | /* 2 | Translation strings used by the bot 3 | Strings wrapped in { } will be auto-replaced with updated values 4 | Globals: {servername}, {invite}, (all from config) and {playercount} (current connected players) 5 | */ 6 | const locale = { 7 | checkingWhitelist: "Hoi {name}, wij kijken na of je wel gewhitelist bent", 8 | discordNotOpen: "Je moet Discord open hebben staan voordat je FiveM opstart (Restart beiden als het probleem zich voordoet)", 9 | fatalError: "Iets ging verkeerd terwijl wij jouw whitelist status nakeken, gelieven even opnieuw te verbinden of contact met ons op te zoeken in discord", 10 | notInDiscordServer: "Je zit niet in de {servername} Discord, gelieve deze te joinen hier: {invite}", 11 | notWhitelisted: "Je hebt de benodigde {servername} Discord role niet om te joinen, ben je wel whitelisted?", 12 | kickedWithoutReason: "Je werd gekickt door staff.", 13 | announcement: "AANKONDIGING", 14 | directMessage: "STAFF", 15 | staffchat: "STAFFCHAT", 16 | }; 17 | 18 | module.exports = locale; 19 | -------------------------------------------------------------------------------- /locales/no.js: -------------------------------------------------------------------------------- 1 | /* no - Norwegian Translation by VikingTheDev ♥ 2 | Translation strings used by the bot 3 | Strings wrapped in { } will be auto-replaced with updated values 4 | Globals: {servername}, {invite}, (all from config) and {playercount} (current connected players) 5 | */ 6 | const locale = { 7 | // Includes {name} + Globals 8 | checkingWhitelist: "Hei {name}, vi sjekker whitelist statusen din..", 9 | discordNotOpen: "Discord må være åpnet før du starter FiveM (Start både Discord og FiveM på nytt om problemet vedvarer)", 10 | fatalError: "Noe gikk galt med å sjekke whitelist statusen din, vennligst prøv igjen senere eller kontakt support på Discord om problemet vedvarer.", 11 | notInDiscordServer: "Du er ikke medlem i {servername} discorden, du kan bli medlem her: {invite}", 12 | notWhitelisted: "Du har ikke Discord rollen i {servername} som kreves for å koble til denne serveren. Sjekk om du er whitelistet", 13 | kickedWithoutReason: "Du har blitt kastet ut av serveren av staff.", 14 | announcement: "KUNNGJØRING", 15 | directMessage: "STAFF", 16 | staffchat: "STAFFCHAT", 17 | }; 18 | 19 | module.exports = locale; -------------------------------------------------------------------------------- /locales/pl.js: -------------------------------------------------------------------------------- 1 | /* pl - Polish Translation by insaneArian ♥ 2 | Translation strings used by the bot 3 | Strings wrapped in { } will be auto-replaced with updated values 4 | Globals: {servername}, {invite}, (all from config) and {playercount} (current connected players) 5 | */ 6 | const locale = { 7 | checkingWhitelist: "Siema {name}, Sprawdzamy czy jesteś na whiteliście..", 8 | discordNotOpen: "Musisz mieć włączonego Discorda przed FiveM'em (zrestartuj oba jeśli problem dalej występuje)", 9 | fatalError: "Coś poszło nie tak przy sprawdzaniu twojej whitelisty, spróbuj jeszcze raz za chwilę, a jeżeli to nie pomoże skontaktuj się z administracją.", 10 | notInDiscordServer: "Nie jesteś na discordzie {servername}, dołącz tutaj: {invite}", 11 | notWhitelisted: "Nie masz odpowiedniej roli na discordzie {servername} aby dołączyć na serwer, jesteś pewny, że masz whiteliste?", 12 | kickedWithoutReason: "Zostałeś wyrzucony przez administrację.", 13 | announcement: "OGŁOSZENIE", 14 | directMessage: "ADMINISTRACJA", 15 | staffchat: "STAFFCHAT", 16 | }; 17 | 18 | module.exports = locale; 19 | -------------------------------------------------------------------------------- /locales/pt.js: -------------------------------------------------------------------------------- 1 | /* pt - Portuguese Translation by KUMApt ♥ 2 | Translation strings used by the bot 3 | Strings wrapped in { } will be auto-replaced with updated values 4 | Globals: {servername}, {invite}, (all from config) and {playercount} (current connected players) 5 | */ 6 | const locale = { 7 | // Includes {name} + Globals 8 | checkingWhitelist: "Olá {name}, Estamos a verificar o estado da tua whitelist...", 9 | discordNotOpen: "O teu discord precisa de estar aberto antes de iniciares o FiveM (Reinicia ambos se o problema se mantiver).", 10 | fatalError: "Foi encontrado um erro ao procurar o estado da tua whitelist, por favor tenta mais tarde ou contacta o suporte no discord se o problema se mantiver.", 11 | notInDiscordServer: "Não estás presente no Discord {servername}, por favor entra no mesmo por aqui: {invite}", 12 | notWhitelisted: "Não tens o cargo necessário no discord {servername} para entrares no servidor, tens a certeza que estás na whitelist?", 13 | kickedWithoutReason: "Foste expulso por um membro da Staff.", 14 | announcement: "ANÚNCIO", 15 | directMessage: "STAFF", 16 | staffchat: "STAFFCHAT", 17 | }; 18 | 19 | module.exports = locale; 20 | -------------------------------------------------------------------------------- /locales/sl.js: -------------------------------------------------------------------------------- 1 | /* sl - Slovenian translation by Synthethics#0001 ♥ 2 | Translation strings used by the bot 3 | Strings wrapped in { } will be auto-replaced with updated values 4 | Globals: {servername}, {invite}, (all from config) and {playercount} (current connected players) 5 | */ 6 | const locale = { 7 | // Includes {name} + Globals 8 | checkingWhitelist: "Pozdravljen {name}, pregledujemo tvoj status na whitelisti..", 9 | discordNotOpen: "Discord mora biti odprt predenj odpreš FiveM. (Ponovno zaženi oba programa, če se težava ponavlja).", 10 | fatalError: "Nekaj se je zalomilo pri preverjanju tvojega whitelist statusa. Prosim preizkusi znova kasneje ali pa kontaktiraj podporo v Discordu, če se težava ponavlja.", 11 | notInDiscordServer: "Nisi v {servername} Discordu, pridruži se tukaj: {invite}", 12 | notWhitelisted: "V {servername} discordu nimaš prave role, da bi se pridružil strežniku. Si prepričan, da si na whitelisti?", 13 | kickedWithoutReason: "Izvržen si bil s strani Staff-a.", 14 | announcement: "OBVESTILO", 15 | directMessage: "STAFF", 16 | }; 17 | 18 | module.exports = locale; 19 | -------------------------------------------------------------------------------- /locales/sv.js: -------------------------------------------------------------------------------- 1 | /* sv - Swedish Translation by ayabolli ♥ 2 | Translation strings used by the bot 3 | Strings wrapped in { } will be auto-replaced with updated values 4 | Globals: {servername}, {invite}, (all from config) and {playercount} (current connected players) 5 | */ 6 | const locale = { 7 | // Includes {name} + Globals 8 | checkingWhitelist: "Hej {name}, Vi kontrollerar nu din whitelist-status..", 9 | discordNotOpen: "Du måste ha Discord igång innan du startar FiveM (Starta om både Discord och FiveM om problemet kvarstår)", 10 | fatalError: "Något gick fel när vi försökte hämta din whiteliststatus. Vänligen försök igen om en stund eller kontakta support på Discord om felet kvarstår.", 11 | notInDiscordServer: "Du är inte inne på {servername}'s Discord, vänligen joina den här: {invite}", 12 | notWhitelisted: "Du har inte rätt Discord-roll för att kunna joina {servername}'s server , Är du whitelistad?", 13 | kickedWithoutReason: "Du har blivit kickad av en staff.", 14 | announcement: "VIKTIGT MEDDELANDE", 15 | directMessage: "STAFF", 16 | staffchat: "STAFFCHAT", 17 | }; 18 | 19 | module.exports = locale; 20 | -------------------------------------------------------------------------------- /locales/tr.js: -------------------------------------------------------------------------------- 1 | /* tr - Turkish Translation by alp1x ♥ 2 | Translation strings used by the bot 3 | Strings wrapped in { } will be auto-replaced with updated values 4 | Globals: {servername}, {invite}, (all from config) and {playercount} (current connected players) 5 | */ 6 | const locale = { 7 | checkingWhitelist: "Merhaba {name}, Whitelist kontrol ediliyor..", 8 | discordNotOpen: "FiveM'i başlatmadan önce Discord açık olmalıdır. Sorun devam ederse ikisinide yeniden başlatın", 9 | fatalError: "Whitelist durumunuz alınırken bir şeyler ters gitti, lütfen biraz sonra tekrar deneyin veya sorun devam ederse destek ile iletişime geçin.", 10 | notInDiscordServer: "{servername} Discord adresinizde bulunmuyorsunuz, katılmak için: {invite}", 11 | notWhitelisted: "{servername} için whitelist rolünüz bulunmamakta.", 12 | kickedWithoutReason: "Yetkili tarafından sunucudan atıldın.", 13 | announcement: "DUYURU", 14 | directMessage: "PERSONEL", 15 | staffchat: "PERSONELSOHBET", 16 | }; 17 | 18 | module.exports = locale; 19 | -------------------------------------------------------------------------------- /locales/vn.js: -------------------------------------------------------------------------------- 1 | /* vn - Vietnamese Translation by xenfovn ♥ 2 | Translation strings used by the bot 3 | Strings wrapped in { } will be auto-replaced with updated values 4 | Globals: {servername}, {invite}, (all from config) and {playercount} (current connected players) 5 | */ 6 | const locale = { 7 | checkingWhitelist: "Xin Chào {name}, Chúng Tôi Đang Kiểm Tra Whitelist Của Bạn..", 8 | discordNotOpen: "Bạn Cần Mở Discord Trước Khi Mở FiveM ( Khởi Động Lại Cả 2 Nếu Vẫn Bị )", 9 | fatalError: "Đã Xảy Ra Lỗi Khi Kiểm Tra Whitelist , Vui Lòng Liên Hệ Đội Ngũ Hỗ Trợ !.", 10 | notInDiscordServer: "Bạn Chưa Tham Gia Discord {servername} , Tham Gia ở Đây: {invite}", 11 | notWhitelisted: "Bạn Chưa Được Cấp Phép Trong Discord {servername} , Hãy Kiểm Tra Lại !", 12 | kickedWithoutReason: "Bạn Đã Bị Đuổi Bởi Quản Trị Viên.", 13 | announcement: "Thông Báo", 14 | directMessage: "Quản Trị Viên", 15 | staffchat: "STAFFCHAT", 16 | }; 17 | 18 | module.exports = locale; 19 | -------------------------------------------------------------------------------- /optional addons/liveplayercount.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of zdiscord. 3 | * Copyright (C) 2021 Tony/zfbx 4 | * source: 5 | * 6 | * This work is licensed under the Creative Commons 7 | * Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ 9 | * or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 10 | * 11 | * This addon enables you to have a voice channel that updates constantly with the current number of players online 12 | * copy this into your `server/addons` folder and edit the voiceChannelId to the channel id you want to use then start your server 13 | * 14 | * DO NOT TRY TO CHANGE THE UPDATE RATE ANY LOWER - doing so will cause your IP to get restricted from the discord api 15 | */ 16 | 17 | class LivePlayerCount { 18 | constructor(z) { 19 | // Id for the voice channel name to update 20 | this.voiceChannelId = "000000000000000000"; 21 | 22 | this.z = z; 23 | on("zdiscord:ready", async () => { 24 | this.syncChannel(); 25 | this.start(); 26 | }); 27 | } 28 | 29 | async start() { 30 | setInterval(() => { 31 | this.syncChannel(); 32 | }, 1000 * 60 * 5); 33 | } 34 | 35 | async syncChannel() { 36 | try { 37 | const guild = this.z.bot.guilds.resolve(this.z.config.DiscordGuildId); 38 | const channel = guild.channels.cache.get(this.voiceChannelId); 39 | channel.setName(`Players Online: ${GetNumPlayerIndices()}`).catch(); 40 | } catch { 41 | // Just incase something unforseen happens 42 | } 43 | } 44 | 45 | } 46 | 47 | module.exports = LivePlayerCount; 48 | -------------------------------------------------------------------------------- /optional addons/timedmessage.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of zdiscord. 3 | * Copyright (C) 2021 Tony/zfbx 4 | * source: 5 | * 6 | * This work is licensed under the Creative Commons 7 | * Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ 9 | * or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 10 | * 11 | * 12 | * This addon sends a message with status updates every x number of minutes in a specified channel 13 | * copy this into your `server/addons` folder and edit the channelId to the channel id you want messages sent. 14 | */ 15 | 16 | const { MessageEmbed } = require("discord.js"); 17 | 18 | 19 | class TimedMessage { 20 | constructor(z) { 21 | // Minutes 22 | this.timerDelay = 5; 23 | // Channel id to send server status updates 24 | this.channelId = "YOUR_CHANNEL_ID_HERE"; 25 | // store message id so we can edit it later 26 | this.messageId = null; 27 | 28 | this.z = z; 29 | on("zdiscord:ready", async () => { 30 | this.post(); 31 | this.start(); 32 | }); 33 | } 34 | 35 | async start() { 36 | setInterval(() => { 37 | this.post(); 38 | }, 1000 * 60 * this.timerDelay); 39 | } 40 | 41 | async post() { 42 | try { 43 | const channel = await this.z.bot.channels.fetch(this.channelId); 44 | const embed = new MessageEmbed() 45 | .setTitle(`${this.z.config.FiveMServerName}`) 46 | .setDescription(`**Uptime:** ${(GetGameTimer() / 1000 / 60).toFixed(2)} minutes 47 | **Direct Connect:** F8 > ${this.z.config.FiveMServerIP} 48 | **Online Players:** ${GetNumPlayerIndices()}/${GetConvar("sv_maxClients", "48")} 49 | **Discord:** [Discord](${this.z.config.DiscordInviteLink})`) 50 | .setColor("#00ff00") 51 | .setTimestamp() 52 | // footer 53 | .setFooter({ name: `Powered by zdiscord` }) 54 | // print("posting message") // post message print debug (uncomment to debug) 55 | if (this.messageId) { // if we have a message id, edit the message 56 | // print("editing message") // edit message print debug (uncomment to debug) 57 | const message = await channel.messages.fetch(this.messageId); 58 | message.edit({ embeds: [embed] }); 59 | } else { // if we don't have a message id, send a new message 60 | // print("sending message") // send message print debug (uncomment to debug) 61 | const message = await channel.send({ embeds: [embed] }); 62 | this.messageId = message.id; 63 | } 64 | } 65 | catch (e) { 66 | console.error(e); 67 | } 68 | } 69 | 70 | } 71 | 72 | module.exports = TimedMessage; 73 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zdiscord", 3 | "version": "7.3.0", 4 | "description": "Advanced Discord Bot Allowlist that's simple to use", 5 | "main": "server.js", 6 | "scripts": { 7 | "start": "node server/server.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/zfbx/zdiscord.git" 12 | }, 13 | "author": "Tony#1275", 14 | "contributors": [ 15 | "zfbx (https://github.com/zfbx/)" 16 | ], 17 | "license": "CC-BY-NC-SA-4.0", 18 | "bugs": { 19 | "url": "https://github.com/zfbx/zdiscord/issues" 20 | }, 21 | "homepage": "https://github.com/zfbx/zdiscord", 22 | "dependencies": { 23 | "discord.js": "github:zfbx/discord.js" 24 | }, 25 | "eslintConfig": { 26 | "extends": "eslint:recommended", 27 | "env": { 28 | "node": true, 29 | "es6": true 30 | }, 31 | "parserOptions": { 32 | "ecmaVersion": 2021, 33 | "sourceType": "script" 34 | }, 35 | "rules": { 36 | "arrow-spacing": [ 37 | "warn", 38 | { 39 | "before": true, 40 | "after": true 41 | } 42 | ], 43 | "comma-dangle": [ 44 | "error", 45 | "always-multiline" 46 | ], 47 | "comma-spacing": "error", 48 | "comma-style": "error", 49 | "curly": [ 50 | "error", 51 | "multi-line", 52 | "consistent" 53 | ], 54 | "dot-location": [ 55 | "error", 56 | "property" 57 | ], 58 | "handle-callback-err": "off", 59 | "indent": [ 60 | "error", 61 | 4 62 | ], 63 | "keyword-spacing": "error", 64 | "max-nested-callbacks": [ 65 | "error", 66 | { 67 | "max": 4 68 | } 69 | ], 70 | "max-statements-per-line": [ 71 | "error", 72 | { 73 | "max": 2 74 | } 75 | ], 76 | "no-console": "off", 77 | "no-empty-function": "error", 78 | "no-floating-decimal": "error", 79 | "no-inline-comments": "error", 80 | "no-lonely-if": "error", 81 | "no-multi-spaces": "error", 82 | "no-multiple-empty-lines": [ 83 | "error", 84 | { 85 | "max": 3, 86 | "maxEOF": 1, 87 | "maxBOF": 0 88 | } 89 | ], 90 | "no-shadow": [ 91 | "error", 92 | { 93 | "allow": [ 94 | "err", 95 | "resolve", 96 | "reject" 97 | ] 98 | } 99 | ], 100 | "no-trailing-spaces": [ 101 | "error" 102 | ], 103 | "no-var": "error", 104 | "object-curly-spacing": [ 105 | "error", 106 | "always" 107 | ], 108 | "prefer-const": "error", 109 | "quotes": [ 110 | "error", 111 | "double" 112 | ], 113 | "semi": [ 114 | "error", 115 | "always" 116 | ], 117 | "space-before-blocks": "error", 118 | "space-before-function-paren": [ 119 | "error", 120 | { 121 | "anonymous": "never", 122 | "named": "never", 123 | "asyncArrow": "always" 124 | } 125 | ], 126 | "space-in-parens": "error", 127 | "space-infix-ops": "error", 128 | "space-unary-ops": "error", 129 | "spaced-comment": "error", 130 | "yoda": "error", 131 | "no-undef": "off", 132 | "no-unused-vars": "off" 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # zdiscord (v7) 2 | 3 | **Note: zdiscord v7 and high REQUIRE FiveM artifacts build 4890 or newer**
4 | zdiscord v5-6 REQUIRES artifacts 4800 or newer.
5 | Older artifacts will require use of the [v4 eris branch](https://github.com/zfbx/zdiscord/tree/eris) instead. 6 | 7 | [Setup](#setup) | [Donate](#donate) | [FAQ](https://zfbx.github.io/zdiscord/faq) | [Support](#support) | [Docs](https://zfbx.github.io/zdiscord) 8 | 9 | ## About 10 | 11 | A Discord bot that runs in FiveM for the purpose of whitelisting, moderation and utilities using [discord.js](https://discord.js.org/). The goal is for this this resource to be easy to setup and expand upon while giving your staff team an easy method of support and moderation of players in game without actually launching FiveM. This resource also heavily support [QBCore](https://github.com/qbcore-framework) in most of it's functionality but it's not required 12 | 13 | 14 | 15 | ## Features 16 | 17 | - Standalone FiveM resource (no external hosting required) 18 | - Uses [Slash commands](https://support.discord.com/hc/en-us/articles/1500000368501-Slash-Commands-FAQ) with help/suggestions 19 | - Moderation tools (kick, ban, inspect, etc) 20 | - [QBCore](https://github.com/qbcore-framework) [commands](https://zfbx.github.io/zdiscord/commands) included! 21 | - Easy to expand and customize with modular [commands](https://zfbx.github.io/zdiscord/commands#add-commands)! 22 | - Can be configured with [convars](https://zfbx.github.io/zdiscord/config) 23 | - Automatic Ace Permission granting system 24 | - [Helpful exports](https://zfbx.github.io/zdiscord/exports) 25 | - bi-directional staff chat 26 | - AND MORE! 27 | 28 | ## Setup 29 | 30 | ### Requirements 31 | - FiveM artifacts build 4890 or higher 32 | - [cfx-server-data](https://github.com/citizenfx/cfx-server-data) in your resources (yarn (`[system]/[builders]/yarn/`) at least) 33 | - Optional: [screenshot-basic](https://github.com/citizenfx/screenshot-basic) if you want the /screenshot command to work 34 | 35 | ### Steps 36 | 1. Get a bot application if you haven't already [Guide Here](https://discordjs.guide/preparations/setting-up-a-bot-application.html) 37 | 38 | 2. **IMPORTANT: Enable BOTH intents** on the **bot** page of step 1 ([Picture example](https://zfbx.github.io/zdiscord/images/intents.png)) *If you don't do this.. your bot will NOT work. 39 | 40 | 3. Add the bot to your server - To do this copy the following link and replace `YOUR-BOT-ID` with your bots ID then follow the invite process to your discord from the link `https://discord.com/api/oauth2/authorize?client_id=YOUR-BOT-ID&permissions=116928&scope=bot%20applications.commands`
**NOTE: If the bot is already in your server you might need to run the link above again anyways to make sure it can get the needed slash command scope (unrelated to permissions)** 41 | 42 | 4. Copy the resource into your fiveM resources directory and make sure it's named `zdiscord` (not zdiscord-djs, zdiscord-eris or anything) 43 | 44 | 5. Double check that you have the [cfx-server-data](https://github.com/citizenfx/cfx-server-data) resource in your resources (or yarn `[system]/[builders]/yarn/` at the very least) 45 | 46 | 6. In your `server.cfg` do the following:
47 | 6a. Add `ensure zdiscord` (after qb-core and/or [convars](https://zfbx.github.io/zdiscord/convars) you may have)
48 | 6b. Add the following anywhere in your .cfg: 49 | ``` 50 | add_ace resource.zdiscord command allow 51 | add_ace group.zdiscordstaff zdiscord.staffchat allow 52 | ``` 53 | 54 | 7. Adjust the `config.js` variables to how you'd like them. (Optionally use [Convars](https://zfbx.github.io/zdiscord/convars)) 55 | 56 | 8. **If you missed step 2, go back and do it.. or else IT WONT WORK!** 57 | 58 | 9. If you run into any errors check out the [FAQ](https://zfbx.github.io/zdiscord/faq) where a lot of common problems are listed and answered 59 | 60 | 61 | ## Support 62 | 63 | *Please note we only support the official, free and open source, [QBCore](https://github.com/qbcore-framework) framework and not old "qbus" or paid copies of QBCore* 64 | 65 | If you have any errors or problems please first check: 66 | - [Frequently Asked Questions](https://zfbx.github.io/zdiscord/faq) 67 | - [Github Issues](https://github.com/zfbx/zdiscord/issues?q=) 68 | 69 | If neither of those solve your problem [Open a ticket](https://github.com/zfbx/zdiscord/issues/new/choose) or message me on [Discord](https://discord.gg/M6neBU3cvP) (My name is Tony#1275 on discord) 70 | 71 | 72 | ## Donate 73 | 74 | I've built and polished this resource from the ground up for free and open sourced it for everybody. If you use it, enjoy it, get support from me or just want to support the project please consider sending a tip or donation through any of the following platforms: 75 | 76 | [![Donate on PayPal](https://img.shields.io/badge/Donate-PayPal-%2300457C?style=for-the-badge&logo=paypal)](https://paypal.me/zfbx) 77 | [![Sub on Patreon](https://img.shields.io/badge/Support-Patreon-%23FF424D?style=for-the-badge&logo=patreon)](https://www.patreon.com/zfbx) 78 | [![Buy Me a Pizza](https://img.shields.io/badge/Pizza-BuyMeACoffee-%23FFDD00?style=for-the-badge&logo=buymeacoffee)](https://www.buymeacoffee.com/zfbx) 79 | 80 | Any contribution is greatly appreciated but you're amazing regardless ♥ 81 | 82 | ## License 83 | 84 | 85 | **Note: as of version 7.0.0 zdiscord, it is licensed under CC-BY-NC-SA-4.0** 86 | 87 |

zdiscord © 2021 by zfbx is licensed under Attribution-NonCommercial-ShareAlike 4.0 International

88 | 89 | **TL;DR** 90 | - BY: Credit must be given to me, the creator. (Tony/zfbx) 91 | - NC: Only noncommercial use of your work is permitted. (You can use in your own FiveM server which may make money itself BUT can't in any way sell zdiscord itself in any way for any commercial advantage or monetary compensation) 92 | - SA: Adaptations must be shared under the same terms. 93 | -------------------------------------------------------------------------------- /server/addons/log.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of zdiscord. 3 | * Copyright (C) 2021 Tony/zfbx 4 | * source: 5 | * 6 | * This work is licensed under the Creative Commons 7 | * Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ 9 | * or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 10 | */ 11 | 12 | const { MessageEmbed, WebhookClient } = require("discord.js"); 13 | 14 | class Log { 15 | constructor(z) { 16 | this.z = z; 17 | this.enabled = z.config.EnableLoggingWebhooks; 18 | this.hooks = {}; 19 | 20 | if (this.enabled) { 21 | StopResource("qb-logs"); 22 | let count = 0; 23 | Object.entries(z.config.LoggingWebhooks).forEach(entry => { 24 | const [key, value] = entry; 25 | const k = key.toLocaleLowerCase(); 26 | if (this.hooks[k]) return client.z.utils.log.write(`Webhook for ${k} has already been registered. Remove the duplicate key`, { tag: "WEBHOOK", error: true }); 27 | this.hooks[k] = new WebhookClient({ url: value.replace(/discordapp/g, "discord") }); 28 | count++; 29 | }); 30 | this.z.utils.log.info(`${count} webhooks loaded.`, { tag: "WEBHOOK" }); 31 | } 32 | global.exports("log", async (type, message, pingRole, color) => { 33 | return z.log.send(type, message, { pingRole: pingRole, color: color }); 34 | }); 35 | } 36 | 37 | /** Sends a log through to a webhook by name configured in config 38 | * @param {string} type - type of log/event to pick which webhook to send 39 | * @param {string} message - Message to log 40 | * @param {boolean} pingRole - Whether message should ping configured role 41 | * @param {object} options - pingRole, color, username, pingId 42 | * @returns {boolean} - success or failure of logging event */ 43 | async send(type, message, options) { 44 | if (!this.enabled) return false; 45 | if (!message || !type) return this.z.utils.log.write("Log without message or type not permitted", { tag: "WEBHOOK", error: true }); 46 | 47 | const hook = this.hooks[type.toLocaleLowerCase()]; 48 | if (!hook) return this.z.utils.log.write(`Webhook "${type}" is not defined. Message: ${message}`, { tag: "WEBHOOK", error: true }); 49 | 50 | const embed = new MessageEmbed().setDescription(message).setColor(options.color || "#1e90ff"); 51 | const data = { 52 | username: options.username || this.z.config.LoggingWebhookName, 53 | embeds: [ embed ], 54 | }; 55 | if (options.pingRole) data.content = `<@${options.pingId || this.z.config.LoggingAlertPingId}>`; 56 | 57 | await hook.send(data).catch((e) => { 58 | return this.z.utils.log.write(`${type.toLowerCase()} log failed. Message: ${message}. Error: ${reply.status}`, { tag: "WEBHOOK", error: true }); 59 | }); 60 | return true; 61 | } 62 | 63 | } 64 | 65 | module.exports = Log; 66 | -------------------------------------------------------------------------------- /server/bot.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of zdiscord. 3 | * Copyright (C) 2021 Tony/zfbx 4 | * source: 5 | * 6 | * This work is licensed under the Creative Commons 7 | * Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ 9 | * or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 10 | */ 11 | 12 | const { Client, Collection, MessageEmbed, MessageActionRow, MessageButton } = require("discord.js"); 13 | const { readdirSync } = require("fs"); 14 | 15 | class Bot extends Client { 16 | 17 | constructor(z) { 18 | super({ 19 | intents: 14335, 20 | fetchAllMembers: true, 21 | messageCacheMaxSize: 10, 22 | }); 23 | this.enabled = z.config.EnableDiscordBot; 24 | this.z = z; 25 | this.config = z.config; 26 | this.QBCore = z.QBCore; 27 | this.log = z.utils.log; 28 | this.utils = z.utils; 29 | this.Embed = MessageEmbed; 30 | this.commands = new Collection(); 31 | this.arrayOfCommands = []; 32 | 33 | if (this.enabled) this.start(); 34 | } 35 | 36 | start() { 37 | this.utils.log.assert((process.version == "v12.13.0"), "You are running unsupported artifacts, download a newer artifact or revert to version 4.0.0 of zdiscord"); 38 | this.utils.log.assert((this.config.DiscordBotToken == "CHANGE"), "This module requires a discord bot token to run. Check the config.js"); 39 | this.utils.log.assert((this.config.DiscordGuildId == "000000000000000000"), "This resource requires a discord guildid to work properly. Check the config.js"); 40 | this.utils.log.assert(!(this.utils.isValidID(this.config.DiscordGuildId)), "Your DiscordGuildId doesn't seem correct"); 41 | this.utils.log.assert(!(this.utils.isValidID(this.config.DiscordModRoleId)), "Your DiscordModRoleId doesn't seem correct"); 42 | this.utils.log.assert(!(this.utils.isValidID(this.config.DiscordAdminRoleId)), "Your DiscordAdminRoleId doesn't seem correct"); 43 | this.utils.log.assert(!(this.utils.isValidID(this.config.DiscordGodRoleId)), "Your DiscordGodRoleId doesn't seem correct"); 44 | this.utils.log.assert(this.config.EnableStaffChatForwarding && !(this.utils.isValidID(this.config.DiscordStaffChannelId)), "Your DiscordStaffChannelId doesn't seem correct"); 45 | 46 | if (this.config.EnableDiscordSlashCommands) this.loadCommands(); 47 | this.loadEvents(); 48 | 49 | if (this.config.DebugLogs) this.on("debug", (debug) => this.log.handler("log", debug)); 50 | this.on("warn", (warning) => this.log.handler("warn", warning)); 51 | this.on("error", (error) => this.log.handler("error", error)); 52 | 53 | this.login(this.config.DiscordBotToken).catch((e) => this.log.handler("error", e)); 54 | } 55 | 56 | loadCommands() { 57 | const commandFiles = readdirSync(`${this.z.root}/server/commands`).filter(file => file.endsWith(".js")); 58 | for (const file of commandFiles) { 59 | const command = require(`${this.z.root}/server/commands/${file}`); 60 | if (!command?.name) continue; 61 | if (command.args || command.alias) { 62 | this.log.warn(`${file} is an v4 or later command and is not supported, upgrade it or remove it`); 63 | continue; 64 | } 65 | if (this.commands.has(command.name)) { 66 | this.log.warn(`${file} is using a name that's already been registered by another command [skipping]`); 67 | continue; 68 | } 69 | if (file.startsWith("qb-") && !this.QBCore) continue; 70 | this.commands.set(command.name, command); 71 | if (["MESSAGE", "USER"].includes(command.type)) delete command.description; 72 | this.arrayOfCommands.push(command); 73 | } 74 | } 75 | 76 | loadEvents() { 77 | const eventFiles = readdirSync(`${this.z.root}/server/events`).filter(file => file.endsWith(".js")); 78 | for (const file of eventFiles) { 79 | const event = require(`${this.z.root}/server/events/${file}`); 80 | if (event.once) this.once(event.name, (...args) => event.run(this, ...args)); 81 | else this.on(event.name, (...args) => event.run(this, ...args)); 82 | } 83 | } 84 | 85 | 86 | /** 87 | * Creates a pagination embed 88 | * credit to https://github.com/ryzyx/discordjs-button-pagination 89 | * @param {Interaction} interaction - the message interaction to react to 90 | * @param {MessageEmbed[]} pages - array of embeds 91 | * @param {MessageButton[]} buttonList - Must be 2 button objects 92 | * @param {number} timeout - Optional length of time to allow responses 93 | * @returns {Interaction} current page 94 | */ 95 | async paginationEmbed(interaction, pages, buttonList, timeout = 120000) { 96 | let page = 0; 97 | const row = new MessageActionRow().addComponents(buttonList); 98 | if (interaction.deferred == false) await interaction.deferReply(); 99 | const curPage = await interaction.editReply({ 100 | embeds: [pages[page].setFooter({ text: `Page ${page + 1} / ${pages.length}` })], 101 | components: [row], fetchReply: true, 102 | }); 103 | const filter = (i) => i.customId === buttonList[0].customId || i.customId === buttonList[1].customId; 104 | const collector = await curPage.createMessageComponentCollector({ filter, time: timeout }); 105 | collector.on("collect", async (i) => { 106 | switch (i.customId) { 107 | case buttonList[0].customId: 108 | page = page > 0 ? --page : pages.length - 1; 109 | break; 110 | case buttonList[1].customId: 111 | page = page + 1 < pages.length ? ++page : 0; 112 | break; 113 | default: 114 | break; 115 | } 116 | await i.deferUpdate(); 117 | await i.editReply({ embeds: [pages[page].setFooter({ text: `Page ${page + 1} / ${pages.length}` })], components: [row] }); 118 | collector.resetTimer(); 119 | }); 120 | collector.on("end", () => { 121 | const disabledRow = new MessageActionRow().addComponents(buttonList[0].setDisabled(true), buttonList[1].setDisabled(true)); 122 | curPage.edit({ embeds: [pages[page].setFooter({ text: `Page ${page + 1} / ${pages.length}` })], components: [disabledRow] }).catch(console.error); 123 | }); 124 | return curPage; 125 | } 126 | 127 | /** Get discord member object by userid 128 | * @param {number} userid - discordid 129 | * @returns {object|boolean} - discord member or false */ 130 | getMember(userid) { 131 | const guild = this.guilds.cache.get(this.config.DiscordGuildId); 132 | if (!guild) { 133 | this.utils.log.error("Failed to fetch Discord server."); 134 | return false; 135 | } 136 | return guild.members.cache.get(userid) || false; 137 | } 138 | 139 | /** Get discord member object by source 140 | * @param {number} id - Player ID / source 141 | * @returns {object|boolean} - discord member or false */ 142 | getMemberFromSource(id) { 143 | const ids = this.utils.getPlayerIdentifiers(id); 144 | if (!ids.discord) return false; 145 | return this.getMember(ids.discord); 146 | } 147 | 148 | /** take any sort of identifier and try to parse a discord member from it 149 | * @param {number|string|object} member - source | discordid | member object 150 | * @returns {object|boolean} - discord member or false */ 151 | parseMember(member) { 152 | if (!member || !this.enabled) return false; 153 | if (typeof member === "number") { 154 | return this.getMemberFromSource(member); 155 | } else if (typeof member === "string") { 156 | return this.getMember(member); 157 | } else { return member || false; } 158 | } 159 | 160 | /** Returns true if a role is found by id or array of ids 161 | * @param {number|object|string} member - source | member | discordid 162 | * @param {string|object} role - Role ID or Role IDs 163 | * @returns {boolean} - true if role was found, false otherwise */ 164 | isRolePresent(member, role) { 165 | if (!role || !member || !this.enabled) return false; 166 | member = this.parseMember(member); 167 | if (!member) return false; 168 | if (typeof role === "object") { 169 | let found = false; 170 | role.forEach(function(item) { 171 | if (member.roles.cache.has(item)) found = true; 172 | }); 173 | return found; 174 | } else { 175 | return member.roles.cache.has(role); 176 | } 177 | } 178 | 179 | /** get array of discord member roles by id 180 | * @param {number|object|string} member - source | member | discordid 181 | * @returns {Array} - array of role ids */ 182 | getMemberRoles(member) { 183 | if (!member || !this.enabled) return []; 184 | member = this.parseMember(member); 185 | if (!member) return []; 186 | return member.roles.cache.map(r => r.id); 187 | } 188 | 189 | hasPermission(member, level) { 190 | switch (level) { 191 | case "mod": 192 | return ( 193 | member.roles.cache.has(this.config.DiscordModRoleId) || 194 | member.roles.cache.has(this.config.DiscordAdminRoleId) || 195 | member.roles.cache.has(this.config.DiscordGodRoleId)); 196 | case "admin": 197 | return ( 198 | member.roles.cache.has(this.config.DiscordAdminRoleId) || 199 | member.roles.cache.has(this.config.DiscordGodRoleId)); 200 | case "god": 201 | return (member.roles.cache.has(this.config.DiscordGodRoleId)); 202 | default: 203 | return true; 204 | } 205 | } 206 | 207 | } 208 | 209 | module.exports = Bot; 210 | -------------------------------------------------------------------------------- /server/commands/announcement.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of zdiscord. 3 | * Copyright (C) 2021 Tony/zfbx 4 | * source: 5 | * 6 | * This work is licensed under the Creative Commons 7 | * Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ 9 | * or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 10 | */ 11 | 12 | module.exports = { 13 | name: "announcement", 14 | description: "Send in city announcement", 15 | role: "mod", 16 | 17 | options: [ 18 | { 19 | name: "message", 20 | description: "announcement to send", 21 | required: true, 22 | type: "STRING", 23 | }, 24 | ], 25 | 26 | run: async (client, interaction, args) => { 27 | client.utils.chatMessage(-1, client.z.locale.announcement, args.message, { color: [ 255, 0, 0 ] }); 28 | client.utils.log.info(`[${interaction.member.displayName}] Announcement: ${args.message}`); 29 | interaction.reply({ content: "Announcement Sent", ephemeral: false }); 30 | }, 31 | }; 32 | -------------------------------------------------------------------------------- /server/commands/embed.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of zdiscord. 3 | * Copyright (C) 2021 Tony/zfbx 4 | * source: 5 | * 6 | * This work is licensed under the Creative Commons 7 | * Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ 9 | * or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 10 | */ 11 | 12 | module.exports = { 13 | name: "embed", 14 | description: "Send an embedded (fancy) message in a specified channel", 15 | role: "god", 16 | 17 | options: [ 18 | { 19 | type: "SUB_COMMAND", 20 | name: "simple", 21 | description: "Simple to use embed creator", 22 | options: [ 23 | { 24 | name: "channel", 25 | description: "Channel to send embed to", 26 | required: true, 27 | type: "CHANNEL", 28 | }, 29 | { 30 | name: "message", 31 | description: "message with markdown support", 32 | required: true, 33 | type: "STRING", 34 | }, 35 | { 36 | name: "title", 37 | description: "embed title (short)", 38 | required: false, 39 | type: "STRING", 40 | }, 41 | { 42 | name: "image", 43 | description: "url of image to embed", 44 | required: false, 45 | type: "STRING", 46 | }, 47 | { 48 | name: "thumbnail", 49 | description: "url of image to use as icon", 50 | required: false, 51 | type: "STRING", 52 | }, 53 | { 54 | name: "footer", 55 | description: "footer message", 56 | required: false, 57 | type: "STRING", 58 | }, 59 | { 60 | name: "color", 61 | description: "embed color (example: #007bff)", 62 | required: false, 63 | type: "STRING", 64 | }, 65 | ], 66 | }, 67 | { 68 | type: "SUB_COMMAND", 69 | name: "complex", 70 | description: "send an embed from a json string", 71 | options: [ 72 | { 73 | name: "channel", 74 | description: "Channel to send embed to", 75 | required: true, 76 | type: "CHANNEL", 77 | }, 78 | { 79 | name: "json", 80 | description: "json string to post", 81 | required: true, 82 | type: "STRING", 83 | }, 84 | ], 85 | }, 86 | ], 87 | 88 | run: async (client, interaction, args) => { 89 | const channel = interaction.guild.channels.cache.get(args.channel); 90 | if (!channel || channel.type !== "GUILD_TEXT") return interaction.reply({ content: "This isn't a valid channel I can post in", ephemeral: true }); 91 | if (args.simple) { 92 | const embed = new client.Embed(); 93 | if (args.title) embed.setTitle(args.title); 94 | if (args.footer) embed.setFooter({ text: args.footer }); 95 | if (args.image) { 96 | if (/^(http[s]?:\/\/.*\.(?:png|jpg|gif|jpeg))/i.test(args.image)) embed.setImage(args.image); 97 | else return interaction.reply({ content: "Image link seems to be invalid", ephemeral: true }); 98 | } 99 | if (args.color) { 100 | if (/^#[0-9A-F]{6}$/i.test(args.color)) embed.setColor(args.color); 101 | else return interaction.reply({ content: "Color is invalid. Provide a hex value (example: #ff22cc)", ephemeral: true }); 102 | } 103 | if (args.thumbnail) { 104 | if (/^(http[s]?:\/\/.*\.(?:png|jpg|gif|jpeg))/i.test(args.thumbnail)) embed.setThumbnail(args.thumbnail); 105 | else return interaction.reply({ content: "Thumbnail link seems to be invalid", ephemeral: true }); 106 | } 107 | embed.setDescription(args.message.replace(/
/ig, "\n")); 108 | channel.send({ embeds: [ embed ] }); 109 | } else if (args.complex) { 110 | let embed = args.json; 111 | try { 112 | embed = JSON.parse(args.json); 113 | } catch (e) { 114 | return interaction.reply({ content: "JSON seems invalid", ephemeral: true }); 115 | } 116 | channel.send({ embeds: [ embed ] }); 117 | } 118 | return interaction.reply({ content: "Embed Published", ephemeral: false }); 119 | }, 120 | }; 121 | -------------------------------------------------------------------------------- /server/commands/identifiers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of zdiscord. 3 | * Copyright (C) 2021 Tony/zfbx 4 | * source: 5 | * 6 | * This work is licensed under the Creative Commons 7 | * Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ 9 | * or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 10 | */ 11 | 12 | module.exports = { 13 | name: "identifiers", 14 | description: "Get all of a player's identifiers", 15 | role: "admin", 16 | 17 | options: [ 18 | { 19 | name: "id", 20 | description: "Player's current id", 21 | required: true, 22 | type: "INTEGER", 23 | }, 24 | ], 25 | 26 | run: async (client, interaction, args) => { 27 | if (!GetPlayerName(args.id)) return interaction.reply({ content: "This ID seems invalid.", ephemeral: true }); 28 | const embed = new client.Embed() 29 | .setColor(client.config.embedColor) 30 | .setTitle(`${GetPlayerName(args.id)}'s identifiers`) 31 | .setFooter({ text: "Please respect privacy and avoid doxing players" }); 32 | let desc = ""; 33 | for (const [key, value] of Object.entries(client.utils.getPlayerIdentifiers(args.id))) { 34 | if (key == "discord") desc += `**${key}:** <@${value}> (${value})\n`; 35 | else desc += `**${key}:** ${value}\n`; 36 | } 37 | embed.setDescription(desc); 38 | client.utils.log.info(`[${interaction.member.displayName}] pulled identifiers on ${GetPlayerName(args.id)} (${args.id})`); 39 | return interaction.reply({ embeds: [embed], ephemeral: true }).catch(); 40 | }, 41 | }; 42 | -------------------------------------------------------------------------------- /server/commands/kick.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of zdiscord. 3 | * Copyright (C) 2021 Tony/zfbx 4 | * source: 5 | * 6 | * This work is licensed under the Creative Commons 7 | * Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ 9 | * or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 10 | */ 11 | 12 | module.exports = { 13 | name: "kick", 14 | description: "Kick a player from the city", 15 | role: "mod", 16 | 17 | options: [ 18 | { 19 | name: "id", 20 | description: "Player's current id", 21 | required: true, 22 | type: "INTEGER", 23 | }, 24 | { 25 | name: "message", 26 | description: "Kick message to show the user", 27 | required: false, 28 | type: "STRING", 29 | }, 30 | ], 31 | 32 | run: async (client, interaction, args) => { 33 | if (!GetPlayerName(args.id)) return interaction.reply({ content: "This ID seems invalid.", ephemeral: true }); 34 | const reason = client.utils.replaceGlobals(client, args.message || client.z.locale.kickedWithoutReason); 35 | DropPlayer(args.id, reason); 36 | client.utils.log.info(`[${interaction.member.displayName}] Kicked ${GetPlayerName(args.id)} (${args.id}). Reason: ${reason}`); 37 | return interaction.reply({ content: `${GetPlayerName(args.id)} (${args.id}) has been kicked.`, ephemeral: false }); 38 | }, 39 | }; 40 | -------------------------------------------------------------------------------- /server/commands/kickall.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of zdiscord. 3 | * Copyright (C) 2021 Tony/zfbx 4 | * source: 5 | * 6 | * This work is licensed under the Creative Commons 7 | * Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ 9 | * or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 10 | */ 11 | 12 | module.exports = { 13 | name: "kickall", 14 | description: "Kick every player in the city", 15 | role: "admin", 16 | 17 | options: [ 18 | { 19 | name: "message", 20 | description: "Kick message to show the user", 21 | required: true, 22 | type: "STRING", 23 | }, 24 | ], 25 | 26 | run: async (client, interaction, args) => { 27 | const numberOnline = GetNumPlayerIndices(); 28 | if (numberOnline === 0) return interaction.reply({ content: "Nobody was online to kick.", ephemeral: false }); 29 | getPlayers().forEach(async (player) => { 30 | DropPlayer(player, args.message); 31 | }); 32 | client.utils.log.info(`[${interaction.member.displayName}] Kicked all ${numberOnline} player(s). Reason: ${args.message}`); 33 | return interaction.reply({ content: `All ${numberOnline} player(s) have been kicked.`, ephemeral: false }); 34 | }, 35 | }; 36 | -------------------------------------------------------------------------------- /server/commands/kill.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of zdiscord. 3 | * Copyright (C) 2021 Tony/zfbx 4 | * source: 5 | * 6 | * This work is licensed under the Creative Commons 7 | * Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ 9 | * or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 10 | */ 11 | 12 | module.exports = { 13 | name: "kill", 14 | description: "kill a player in city", 15 | role: "admin", 16 | 17 | options: [ 18 | { 19 | name: "id", 20 | description: "Player's current id", 21 | required: true, 22 | type: "INTEGER", 23 | }, 24 | ], 25 | 26 | run: async (client, interaction, args) => { 27 | if (!GetPlayerName(args.id)) return interaction.reply({ content: "This ID seems invalid.", ephemeral: true }); 28 | emitNet(`${GetCurrentResourceName()}:kill`, args.id); 29 | client.utils.log.info(`[${interaction.member.displayName}] Killed ${GetPlayerName(args.id)} (${args.id})`); 30 | return interaction.reply({ content: `${GetPlayerName(args.id)} (${args.id}) has been murdered.`, ephemeral: false }); 31 | }, 32 | }; 33 | -------------------------------------------------------------------------------- /server/commands/message.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of zdiscord. 3 | * Copyright (C) 2021 Tony/zfbx 4 | * source: 5 | * 6 | * This work is licensed under the Creative Commons 7 | * Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ 9 | * or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 10 | */ 11 | 12 | module.exports = { 13 | name: "message", 14 | description: "direct a message to a specific player", 15 | role: "mod", 16 | 17 | options: [ 18 | { 19 | name: "id", 20 | description: "Player's current id", 21 | required: true, 22 | type: "INTEGER", 23 | }, 24 | { 25 | name: "message", 26 | description: "Message for player", 27 | required: true, 28 | type: "STRING", 29 | }, 30 | ], 31 | 32 | run: async (client, interaction, args) => { 33 | if (!GetPlayerName(args.id)) return interaction.reply({ content: "This ID seems invalid.", ephemeral: true }); 34 | client.utils.chatMessage(args.id, client.z.locale.directMessage, args.message); 35 | client.utils.log.info(`[${interaction.member.displayName}] sent a DM to ${GetPlayerName(args.id)} (${args.id}): ${args.message}`); 36 | return interaction.reply({ content: `Message sent to ${GetPlayerName(args.id)} (${args.id}).`, ephemeral: false }); 37 | }, 38 | }; 39 | -------------------------------------------------------------------------------- /server/commands/onlinecount.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of zdiscord. 3 | * Copyright (C) 2021 Tony/zfbx 4 | * source: 5 | * 6 | * This work is licensed under the Creative Commons 7 | * Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ 9 | * or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 10 | */ 11 | 12 | module.exports = { 13 | name: "onlinecount", 14 | description: "Number of players currently online", 15 | 16 | run: async (client, interaction) => { 17 | const playerNumber = GetNumPlayerIndices(); 18 | let message = "Nobody is online right now."; 19 | if (playerNumber === 1) message = "There is 1 person online right now."; 20 | else if (playerNumber > 1) message = `There are ${playerNumber} people online right now.`; 21 | return interaction.reply({ content: message, ephemeral: false }); 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /server/commands/players.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of zdiscord. 3 | * Copyright (C) 2021 Tony/zfbx 4 | * source: 5 | * 6 | * This work is licensed under the Creative Commons 7 | * Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ 9 | * or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 10 | */ 11 | 12 | const { MessageButton } = require("discord.js"); 13 | 14 | module.exports = { 15 | name: "players", 16 | description: "Get list of current players in city", 17 | role: "mod", 18 | 19 | run: async (client, interaction) => { 20 | if (GetNumPlayerIndices() === 0) return interaction.reply({ content: "Nobody is currently online to pull", ephemeral: false }); 21 | const parts = []; 22 | let index = 0; 23 | getPlayers().sort().forEach((id) => { 24 | const i = Math.floor(index / 10); 25 | if (!parts[i]) parts[i] = ""; 26 | parts[i] += `\`[${id}]\` **${GetPlayerName(id)}**`; 27 | if (client.QBCore) { 28 | try { 29 | const player = client.QBCore.Functions.GetPlayer(parseInt(id)); 30 | parts[i] += ` | (${player.PlayerData.citizenid}) **${player.PlayerData.charinfo.firstname} ${player.PlayerData.charinfo.lastname}**\n`; 31 | } catch { parts[i] += " (Not yet loaded)\n"; } 32 | } else { parts[i] += "\n"; } 33 | index++; 34 | }); 35 | const pages = []; 36 | parts.forEach((part) => { 37 | const embed = new client.Embed() 38 | .setTitle(`Players (${GetNumPlayerIndices()})`) 39 | .setDescription(`${part}`); 40 | pages.push(embed); 41 | }); 42 | const backBtn = new MessageButton().setCustomId("previousbtn").setEmoji("🔺").setStyle("SECONDARY"); 43 | const forwardBtn = new MessageButton().setCustomId("nextbtn").setEmoji("🔻").setStyle("SECONDARY"); 44 | client.paginationEmbed(interaction, pages, [backBtn, forwardBtn]); 45 | }, 46 | }; 47 | -------------------------------------------------------------------------------- /server/commands/qb-ban.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of zdiscord. 3 | * Copyright (C) 2021 Tony/zfbx 4 | * source: 5 | * 6 | * This work is licensed under the Creative Commons 7 | * Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ 9 | * or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 10 | */ 11 | 12 | module.exports = { 13 | name: "ban", 14 | description: "ban a player", 15 | role: "admin", 16 | 17 | options: [ 18 | { 19 | name: "id", 20 | description: "Player's current id", 21 | required: true, 22 | type: "INTEGER", 23 | }, 24 | { 25 | name: "time", 26 | description: "How long in seconds to ban player for", 27 | required: true, 28 | type: "INTEGER", 29 | }, 30 | { 31 | name: "reason", 32 | description: "reason for ban", 33 | required: true, 34 | type: "STRING", 35 | }, 36 | ], 37 | 38 | run: async (client, interaction, args) => { 39 | if (!GetPlayerName(args.id)) return interaction.reply({ content: "This ID seems invalid.", ephemeral: true }); 40 | if (args.time < 0) return interaction.reply({ content: "time must be a positive number", ephemeral: true }); 41 | // const player = client.QBCore.Functions.GetPlayer(args.id); 42 | /* If this event is fixed the code following can be removed. 43 | emit("qb-admin:server:ban", player, time, reason); 44 | */ 45 | const bantime = args.time < 2147483647 ? (args.time + Math.floor(Date.now() / 1000)) : 2147483647; 46 | global.exports.oxmysql.insert_async("INSERT INTO bans (name, license, discord, ip, reason, expire, bannedby) VALUES (?, ?, ?, ?, ?, ?, ?)", [ 47 | GetPlayerName(args.id), 48 | client.QBCore.Functions.GetIdentifier(args.id, "license"), 49 | client.QBCore.Functions.GetIdentifier(args.id, "discord"), 50 | client.QBCore.Functions.GetIdentifier(args.id, "ip"), 51 | args.reason, 52 | bantime, 53 | interaction.member.id, 54 | ]); 55 | client.utils.chatMessage(-1, client.z.locale.announcement, `${GetPlayerName(args.id)} has been banned for breaking the rules.`, { color: [ 155, 0, 0 ] }); 56 | emit("qb-log:server:CreateLog", "bans", "Player Banned", "red", `${GetPlayerName(args.id)} was banned by ${interaction.member.displayName} for ${args.reason}`, true); 57 | if (bantime >= 2147483647) { 58 | DropPlayer(args.id, `You have been banned:\n${args.reason}\n\nYour ban is permanent.\n🔸 Check our Discord for more information: ${client.QBCore.Config.Server.discord}`); 59 | } else { 60 | DropPlayer(args.id, `You have been banned:\n${args.reason}\n\nBan expires in ${args.time / 60} minutes\n🔸 Check our Discord for more information: ${client.QBCore.Config.Server.discord}`); 61 | } 62 | 63 | // End of filler code 64 | 65 | client.utils.log.info(`[${interaction.member.displayName}] banned ${GetPlayerName(args.id)} (${args.id}) for ${args.time} seconds`); 66 | return interaction.reply({ content: `${GetPlayerName(args.id)} (${args.id}) was banned for ${args.time / 60} minutes.`, ephemeral: false }); 67 | }, 68 | }; 69 | -------------------------------------------------------------------------------- /server/commands/qb-clothingmenu.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of zdiscord. 3 | * Copyright (C) 2021 Tony/zfbx 4 | * source: 5 | * 6 | * This work is licensed under the Creative Commons 7 | * Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ 9 | * or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 10 | */ 11 | 12 | module.exports = { 13 | name: "clothing-menu", 14 | description: "Give a player the clothing menu", 15 | role: "admin", 16 | 17 | options: [ 18 | { 19 | name: "id", 20 | description: "Player's current id", 21 | required: true, 22 | type: "INTEGER", 23 | }, 24 | ], 25 | 26 | run: async (client, interaction, args) => { 27 | if (!GetPlayerName(args.id)) return interaction.reply({ content: "This ID seems invalid.", ephemeral: true }); 28 | emitNet("qb-clothing:client:openMenu", args.id); 29 | client.utils.log.info(`[${interaction.member.displayName}] gave ${args.id} the clothing menu`); 30 | return interaction.reply({ content: `${GetPlayerName(args.id)} (${args.id}) was given the clothing menu`, ephemeral: false }); 31 | }, 32 | }; 33 | -------------------------------------------------------------------------------- /server/commands/qb-gang.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of zdiscord. 3 | * Copyright (C) 2021 Tony/zfbx 4 | * source: 5 | * 6 | * This work is licensed under the Creative Commons 7 | * Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ 9 | * or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 10 | */ 11 | 12 | module.exports = { 13 | name: "gang", 14 | description: "Manage player's in-city gang", 15 | role: "admin", 16 | 17 | options: [ 18 | { 19 | type: "SUB_COMMAND", 20 | name: "set", 21 | description: "set a player's gang", 22 | options: [ 23 | { 24 | name: "id", 25 | description: "Player's current id", 26 | required: true, 27 | type: "INTEGER", 28 | }, 29 | { 30 | name: "gang", 31 | description: "gang to set", 32 | required: true, 33 | type: "STRING", 34 | }, 35 | { 36 | name: "grade", 37 | description: "gang grade (or rank)", 38 | required: true, 39 | type: "STRING", 40 | choices: [ 41 | { name: "Grade 0", value: "0" }, 42 | { name: "Grade 1", value: "1" }, 43 | { name: "Grade 2", value: "2" }, 44 | { name: "Grade 3", value: "3" }, 45 | { name: "Grade 4", value: "4" }, 46 | { name: "Grade 5", value: "5" }, 47 | { name: "Grade 6", value: "6" }, 48 | { name: "Grade 7", value: "7" }, 49 | { name: "Grade 8", value: "8" }, 50 | { name: "Grade 9", value: "9" }, 51 | { name: "Grade 10", value: "10" }, 52 | ], 53 | }, 54 | ], 55 | }, 56 | { 57 | type: "SUB_COMMAND", 58 | name: "kick", 59 | description: "kick a player from their current gang", 60 | options: [ 61 | { 62 | name: "id", 63 | description: "Player's current id", 64 | required: true, 65 | type: "INTEGER", 66 | }, 67 | ], 68 | }, 69 | { 70 | type: "SUB_COMMAND", 71 | name: "inspect", 72 | description: "Get a player's current gang", 73 | options: [ 74 | { 75 | name: "id", 76 | description: "Player's current id", 77 | required: true, 78 | type: "INTEGER", 79 | }, 80 | ], 81 | }, 82 | ], 83 | 84 | run: async (client, interaction, args) => { 85 | if (!GetPlayerName(args.id)) return interaction.reply({ content: "This ID seems invalid.", ephemeral: true }); 86 | const player = client.QBCore.Functions.GetPlayer(args.id); 87 | const prevGang = `${player.PlayerData.gang.name} (${player.PlayerData.gang.grade.level})`; 88 | if (args.set) { 89 | if (player.Functions.SetGang(args.gang, args.grade)) { 90 | client.utils.log.info(`[${interaction.member.displayName}] changed ${GetPlayerName(args.id)} (${args.id})'s gang from ${prevGang} to ${args.gang} (${args.grade})`); 91 | return interaction.reply({ content: `${GetPlayerName(args.id)} (${args.id}) was moved from gang ${prevGang} to ${args.gang} (${args.grade})`, ephemeral: false }); 92 | } else { 93 | return interaction.reply({ content: "Invalid gang or grade", ephemeral: false }); 94 | } 95 | } else if (args.kick) { 96 | player.Functions.SetGang("none", "0"); 97 | client.utils.log.info(`[${interaction.member.displayName}] kicked ${GetPlayerName(args.id)} ${args.id} from their gang as ${prevGang}`); 98 | return interaction.reply({ content: `${GetPlayerName(args.id)} (${args.id}) has been kicked from a gang: ${prevGang}`, ephemeral: false }); 99 | } else if (args.inspect) { 100 | return interaction.reply({ content: `${GetPlayerName(args.id)} (${args.id}) is in gang ${prevGang}`, ephemeral: false }); 101 | } 102 | }, 103 | }; 104 | -------------------------------------------------------------------------------- /server/commands/qb-inventory.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of zdiscord. 3 | * Copyright (C) 2021 Tony/zfbx 4 | * source: 5 | * 6 | * This work is licensed under the Creative Commons 7 | * Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ 9 | * or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 10 | */ 11 | 12 | module.exports = { 13 | name: "inventory", 14 | description: "Manage player's in-city items", 15 | role: "admin", 16 | 17 | options: [ 18 | { 19 | type: "SUB_COMMAND", 20 | name: "give", 21 | description: "give a player an item", 22 | options: [ 23 | { 24 | name: "id", 25 | description: "Player's current id", 26 | required: true, 27 | type: "INTEGER", 28 | }, 29 | { 30 | name: "item", 31 | description: "item to give", 32 | required: true, 33 | type: "STRING", 34 | }, 35 | { 36 | name: "count", 37 | description: "how many to give [Default: 1]", 38 | required: false, 39 | type: "INTEGER", 40 | }, 41 | ], 42 | }, 43 | { 44 | type: "SUB_COMMAND", 45 | name: "take", 46 | description: "take an item away from a player", 47 | options: [ 48 | { 49 | name: "id", 50 | description: "Player's current id", 51 | required: true, 52 | type: "INTEGER", 53 | }, 54 | { 55 | name: "item", 56 | description: "item to take", 57 | required: true, 58 | type: "STRING", 59 | }, 60 | { 61 | name: "count", 62 | description: "how many to take [Default: 1]", 63 | required: false, 64 | type: "INTEGER", 65 | }, 66 | ], 67 | }, 68 | { 69 | type: "SUB_COMMAND", 70 | name: "inspect", 71 | description: "Peek inside player's inventory", 72 | options: [ 73 | { 74 | name: "id", 75 | description: "Player's current id", 76 | required: true, 77 | type: "INTEGER", 78 | }, 79 | ], 80 | }, 81 | ], 82 | 83 | run: async (client, interaction, args) => { 84 | const amount = args.count || 1; 85 | if (!GetPlayerName(args.id)) return interaction.reply({ content: "This ID seems invalid.", ephemeral: true }); 86 | const player = client.QBCore.Functions.GetPlayer(args.id); 87 | if (args.give) { 88 | const badItems = [ "id_card", "harness", "markedbills", "labkey", "printerdocument"]; 89 | const itemData = client.QBCore.Shared.Items[args.item.toLowerCase()]; 90 | if (!itemData) return interaction.reply({ content: "Item could not be found", ephemeral: false }); 91 | if (badItems.includes(itemData["name"])) return interaction.reply({ content: "This is a unique item and can't be interacted with like this", ephemeral: false }); 92 | if (amount > 1 && itemData.unique) return interaction.reply({ content: "These items don't stack, give 1 at a time.", ephemeral: false }); 93 | if (player.Functions.AddItem(itemData["name"], amount, false)) { 94 | client.utils.log.info(`[${interaction.member.displayName}] gave ${GetPlayerName(args.id)} (${args.id}) ${amount} ${args.item}`); 95 | return interaction.reply({ content: `${GetPlayerName(args.id)} (${args.id}) was given ${amount} ${itemData.label}`, ephemeral: false }); 96 | } else { 97 | return interaction.reply({ content: "Something went wrong trying to give this item", ephemeral: false }); 98 | } 99 | } else if (args.take) { 100 | const itemData = client.QBCore.Shared.Items[args.item.toLowerCase()]; 101 | if (!itemData) return interaction.reply({ content: "Item could not be found", ephemeral: false }); 102 | if (player.Functions.RemoveItem(args.item, amount)) { 103 | client.utils.log.info(`[${interaction.member.displayName}] removed item from ${GetPlayerName(args.id)}'s (${args.id}) inventory (${amount} ${args.item})`); 104 | return interaction.reply({ content: `${amount} ${itemData.label} has been taken from ${GetPlayerName(args.id)} (${args.id})`, ephemeral: false }); 105 | } else { 106 | return interaction.reply({ content: `Failed to remove item from ${GetPlayerName(args.id)}'s (${args.id}) inventory`, ephemeral: false }); 107 | } 108 | } else if (args.inspect) { 109 | const embed = new client.Embed().setTitle(`${GetPlayerName(args.id)}'s (${args.id}) Inventory`); 110 | const items = player.PlayerData.items; 111 | let desc = ""; 112 | if (typeof items === "object") { 113 | Object.entries(items).forEach(([key, i]) => { 114 | desc += `[${i.slot}] ${i.amount}x - **${i.label}** (${i.name})\n`; 115 | }); 116 | } else { 117 | items.forEach((i) => { 118 | desc += `[${i.slot}] ${i.amount}x - **${i.label}** (${i.name})\n`; 119 | }); 120 | } 121 | embed.setDescription(desc); 122 | return interaction.reply({ embeds: [ embed ], ephemeral: false }); 123 | } 124 | 125 | }, 126 | }; 127 | -------------------------------------------------------------------------------- /server/commands/qb-jail.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of zdiscord. 3 | * Copyright (C) 2021 Tony/zfbx 4 | * source: 5 | * 6 | * This work is licensed under the Creative Commons 7 | * Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ 9 | * or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 10 | */ 11 | 12 | module.exports = { 13 | name: "jail", 14 | description: "Manage a player's jail sentence", 15 | role: "mod", 16 | 17 | options: [ 18 | { 19 | type: "SUB_COMMAND", 20 | name: "sentence", 21 | description: "place player in jail", 22 | options: [ 23 | { 24 | name: "id", 25 | description: "Player's current id", 26 | required: true, 27 | type: "INTEGER", 28 | }, 29 | { 30 | name: "time", 31 | description: "How long in minutes to jail player for", 32 | required: true, 33 | type: "INTEGER", 34 | }, 35 | ], 36 | }, 37 | { 38 | type: "SUB_COMMAND", 39 | name: "free", 40 | description: "free player from jail", 41 | options: [ 42 | { 43 | name: "id", 44 | description: "Player's current id", 45 | required: true, 46 | type: "INTEGER", 47 | }, 48 | ], 49 | }, 50 | ], 51 | 52 | run: async (client, interaction, args) => { 53 | if (!GetPlayerName(args.id)) return interaction.reply({ content: "This ID seems invalid.", ephemeral: true }); 54 | if (args.sentence) { 55 | if (args.time < 5) return interaction.reply({ content: "Jail time need to be more than 5 seconds", ephemeral: true }); 56 | const player = client.QBCore.Functions.GetPlayer(args.id); 57 | const d = new Date(); 58 | // Stupid hack to replicate lua's os.date("*t") for the prison jail script is stupid.. 59 | const currentDate = { 60 | ["month"]: d.getDate(), 61 | ["sec"]: d.getSeconds(), 62 | ["year"]: d.getFullYear(), 63 | ["day"]: (d.getDate() > 30) ? 30 : d.getDate(), 64 | ["min"]: d.getMinutes(), 65 | ["wday"]: d.getDay() + 1, 66 | ["isdst"]: false, 67 | ["yday"]: (Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()) - Date.UTC(d.getFullYear(), 0, 0)) / 24 / 60 / 60 / 1000, 68 | ["hour"]: d.getHours(), 69 | }; 70 | player.Functions.SetMetaData("injail", args.time); 71 | player.Functions.SetMetaData("criminalrecord", { ["hasRecord"]: true, ["date"]: currentDate }); 72 | emitNet("police:client:SendToJail", args.id, parseInt(args.time)); 73 | emitNet("QBCore:Notify", args.id, `You were sent to prison for ${args.time} months`); 74 | client.utils.log.info(`[${interaction.member.displayName}] jailed ${GetPlayerName(args.id)} (${args.id}) for ${args.time} seconds`); 75 | return interaction.reply({ content: `${GetPlayerName(args.id)} (${args.id}) was jailed for ${args.time} months.`, ephemeral: false }); 76 | } else if (args.free) { 77 | emitNet("prison:client:UnjailPerson", args.id); 78 | client.utils.log.info(`[${interaction.member.displayName}] freed ${GetPlayerName(args.id)} (${args.id}) from jail`); 79 | return interaction.reply({ content: `${GetPlayerName(args.id)} (${args.id}) was set free`, ephemeral: false }); 80 | } 81 | }, 82 | }; 83 | -------------------------------------------------------------------------------- /server/commands/qb-job.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of zdiscord. 3 | * Copyright (C) 2021 Tony/zfbx 4 | * source: 5 | * 6 | * This work is licensed under the Creative Commons 7 | * Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ 9 | * or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 10 | */ 11 | 12 | module.exports = { 13 | name: "job", 14 | description: "Manage player's in-city job", 15 | role: "admin", 16 | 17 | options: [ 18 | { 19 | type: "SUB_COMMAND", 20 | name: "set", 21 | description: "set a player's job", 22 | options: [ 23 | { 24 | name: "id", 25 | description: "Player's current id", 26 | required: true, 27 | type: "INTEGER", 28 | }, 29 | { 30 | name: "job", 31 | description: "job to give", 32 | required: true, 33 | type: "STRING", 34 | /* choices: [ // If someone desired they could hard-code options to make it easier but there's a limit of 25 options allowed 35 | { name: "Doctor / Ambulance", value: "ambulance" }, 36 | { name: "Police", value: "police" }, 37 | ],*/ 38 | }, 39 | { 40 | name: "grade", 41 | description: "job grade (some grades may not exist for some jobs)", 42 | required: true, 43 | type: "STRING", 44 | choices: [ 45 | { name: "Grade 0", value: "0" }, 46 | { name: "Grade 1", value: "1" }, 47 | { name: "Grade 2", value: "2" }, 48 | { name: "Grade 3", value: "3" }, 49 | { name: "Grade 4", value: "4" }, 50 | { name: "Grade 5", value: "5" }, 51 | { name: "Grade 6", value: "6" }, 52 | { name: "Grade 7", value: "7" }, 53 | { name: "Grade 8", value: "8" }, 54 | { name: "Grade 9", value: "9" }, 55 | { name: "Grade 10", value: "10" }, 56 | { name: "Grade 11", value: "11" }, 57 | { name: "Grade 12", value: "12" }, 58 | { name: "Grade 13", value: "13" }, 59 | { name: "Grade 14", value: "14" }, 60 | { name: "Grade 15", value: "15" }, 61 | { name: "Grade 16", value: "16" }, 62 | { name: "Grade 17", value: "17" }, 63 | { name: "Grade 18", value: "18" }, 64 | { name: "Grade 19", value: "19" }, 65 | { name: "Grade 20", value: "20" }, 66 | ], 67 | }, 68 | ], 69 | }, 70 | { 71 | type: "SUB_COMMAND", 72 | name: "fire", 73 | description: "fire player from their current job", 74 | options: [ 75 | { 76 | name: "id", 77 | description: "Player's current id", 78 | required: true, 79 | type: "INTEGER", 80 | }, 81 | ], 82 | }, 83 | { 84 | type: "SUB_COMMAND", 85 | name: "inspect", 86 | description: "Get a player's current job", 87 | options: [ 88 | { 89 | name: "id", 90 | description: "Player's current id", 91 | required: true, 92 | type: "INTEGER", 93 | }, 94 | ], 95 | }, 96 | ], 97 | 98 | run: async (client, interaction, args) => { 99 | if (!GetPlayerName(args.id)) return interaction.reply({ content: "This ID seems invalid.", ephemeral: true }); 100 | const player = client.QBCore.Functions.GetPlayer(args.id); 101 | const prevJob = `${player.PlayerData.job.name} (${player.PlayerData.job.grade.level})`; 102 | if (args.set) { 103 | if (player.Functions.SetJob(args.job, args.grade)) { 104 | client.utils.log.info(`[${interaction.member.displayName}] changed ${GetPlayerName(args.id)} (${args.id})'s job from ${prevJob} to ${args.job} (${args.grade})`); 105 | return interaction.reply({ content: `${GetPlayerName(args.id)} (${args.id}) was moved from ${prevJob} to ${args.job} (${args.grade})`, ephemeral: false }); 106 | } else { 107 | return interaction.reply({ content: "Invalid job or grade", ephemeral: false }); 108 | } 109 | } else if (args.fire) { 110 | player.Functions.SetJob("unemployed", "0"); 111 | client.utils.log.info(`[${interaction.member.displayName}] Fired ${GetPlayerName(args.id)} ${args.id} from their job as ${prevJob}`); 112 | return interaction.reply({ content: `${GetPlayerName(args.id)} (${args.id}) has been fired from ${prevJob}`, ephemeral: false }); 113 | } else if (args.inspect) { 114 | return interaction.reply({ content: `${GetPlayerName(args.id)} (${args.id}) has is a ${prevJob}`, ephemeral: false }); 115 | } 116 | }, 117 | }; 118 | -------------------------------------------------------------------------------- /server/commands/qb-logout.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of zdiscord. 3 | * Copyright (C) 2021 Tony/zfbx 4 | * source: 5 | * 6 | * This work is licensed under the Creative Commons 7 | * Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ 9 | * or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 10 | */ 11 | 12 | module.exports = { 13 | name: "logout", 14 | description: "send a player back to the character selection screen", 15 | role: "admin", 16 | 17 | options: [ 18 | { 19 | name: "id", 20 | description: "Player's current id", 21 | required: true, 22 | type: "INTEGER", 23 | }, 24 | ], 25 | 26 | run: async (client, interaction, args) => { 27 | if (!GetPlayerName(args.id)) return interaction.reply({ content: "This ID seems invalid.", ephemeral: true }); 28 | 29 | client.QBCore.Player.Logout(args.id); 30 | emitNet("qb-multicharacter:client:chooseChar", args.id); 31 | 32 | client.utils.log.info(`[${interaction.member.displayName}] logged ${GetPlayerName(args.id)} (${args.id}) out`); 33 | return interaction.reply({ content: `${GetPlayerName(args.id)} (${args.id}) was sent to the character screen.`, ephemeral: false }); 34 | }, 35 | }; 36 | -------------------------------------------------------------------------------- /server/commands/qb-money.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of zdiscord. 3 | * Copyright (C) 2021 Tony/zfbx 4 | * source: 5 | * 6 | * This work is licensed under the Creative Commons 7 | * Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ 9 | * or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 10 | */ 11 | 12 | module.exports = { 13 | name: "money", 14 | description: "Manage player's in-city money", 15 | role: "admin", 16 | 17 | options: [ 18 | { 19 | type: "SUB_COMMAND", 20 | name: "add", 21 | description: "add money to player", 22 | options: [ 23 | { 24 | name: "id", 25 | description: "Player's current id", 26 | required: true, 27 | type: "INTEGER", 28 | }, 29 | { 30 | name: "moneytype", 31 | description: "type of money to add to", 32 | required: true, 33 | type: "STRING", 34 | choices: [ 35 | { name: "Cash", value: "cash" }, 36 | { name: "Bank", value: "bank" }, 37 | { name: "Crypto", value: "Crypto" }, 38 | ], 39 | }, 40 | { 41 | name: "amount", 42 | description: "amount to add", 43 | required: true, 44 | type: "INTEGER", 45 | }, 46 | ], 47 | }, 48 | { 49 | type: "SUB_COMMAND", 50 | name: "remove", 51 | description: "remove money from player", 52 | options: [ 53 | { 54 | name: "id", 55 | description: "Player's current id", 56 | required: true, 57 | type: "INTEGER", 58 | }, 59 | { 60 | name: "moneytype", 61 | description: "type of money to remove from", 62 | required: true, 63 | type: "STRING", 64 | choices: [ 65 | { name: "Cash", value: "cash" }, 66 | { name: "Bank", value: "bank" }, 67 | { name: "Crypto", value: "Crypto" }, 68 | ], 69 | }, 70 | { 71 | name: "amount", 72 | description: "amount to remove", 73 | required: true, 74 | type: "INTEGER", 75 | }, 76 | ], 77 | }, 78 | { 79 | type: "SUB_COMMAND", 80 | name: "set", 81 | description: "set a player's money (OVERWRITE)", 82 | options: [ 83 | { 84 | name: "id", 85 | description: "Player's current id", 86 | required: true, 87 | type: "INTEGER", 88 | }, 89 | { 90 | name: "moneytype", 91 | description: "type of money to set", 92 | required: true, 93 | type: "STRING", 94 | choices: [ 95 | { name: "Cash", value: "cash" }, 96 | { name: "Bank", value: "bank" }, 97 | { name: "Crypto", value: "Crypto" }, 98 | ], 99 | }, 100 | { 101 | name: "amount", 102 | description: "amount to set to", 103 | required: true, 104 | type: "INTEGER", 105 | }, 106 | ], 107 | }, 108 | { 109 | type: "SUB_COMMAND", 110 | name: "inspect", 111 | description: "get a player's current financial stats", 112 | options: [ 113 | { 114 | name: "id", 115 | description: "Player's current id", 116 | required: true, 117 | type: "INTEGER", 118 | }, 119 | ], 120 | }, 121 | ], 122 | 123 | run: async (client, interaction, args) => { 124 | if (!GetPlayerName(args.id)) return interaction.reply({ content: "This ID seems invalid.", ephemeral: true }); 125 | const player = client.QBCore.Functions.GetPlayer(args.id); 126 | const characterName = `${player.PlayerData.charinfo.firstname} ${player.PlayerData.charinfo.lastname}`; 127 | const reason = "Staff intervention"; 128 | if (args.inspect) { 129 | const embed = new client.Embed().setTitle(`${characterName}'s Money`); 130 | let desc = ""; 131 | Object.entries(player.PlayerData.money).forEach(([type, value]) => { 132 | desc += `**${type}:** $${value.toLocaleString("en-US")}\n`; 133 | }); 134 | embed.setDescription(desc); 135 | return interaction.reply({ embeds: [ embed ], ephemeral: false }); 136 | } 137 | if (args.amount < 0) return interaction.reply({ content: "Please only use positive amounts", ephemeral: true }); 138 | const prevMoney = player.Functions.GetMoney(args.moneytype); 139 | if (args.add) { 140 | if (player.Functions.AddMoney(args.moneytype, args.amount, reason)) { 141 | client.utils.log.info(`[${interaction.member.displayName}] Added ${args.amount} to ${GetPlayerName(args.id)} (${args.id})'s ${args.moneytype} [Previously: ${prevMoney}]`); 142 | return interaction.reply({ content: `${characterName} (${args.id})'s ${args.moneytype} has increased from ${prevMoney} to ${player.Functions.GetMoney(args.moneytype)}`, ephemeral: false }); 143 | } else { 144 | return interaction.reply({ content: "Something went wrong trying to add money to this player", ephemeral: false }); 145 | } 146 | } else if (args.remove) { 147 | if (player.Functions.RemoveMoney(args.moneytype, args.amount, reason)) { 148 | client.utils.log.info(`[${interaction.member.displayName}] Removed ${args.amount} from ${GetPlayerName(args.id)} (${args.id})'s ${args.moneytype} [Previously: ${prevMoney}]`); 149 | return interaction.reply({ content: `${characterName} (${args.id})'s ${args.moneytype} has decreased from ${prevMoney} to ${player.Functions.GetMoney(args.moneytype)}`, ephemeral: false }); 150 | } else { 151 | return interaction.reply({ content: "Something went wrong trying to remove money from this player", ephemeral: false }); 152 | } 153 | } else if (args.set) { 154 | if (player.Functions.SetMoney(args.moneytype, args.amount, reason)) { 155 | client.utils.log.info(`[${interaction.member.displayName}] Set ${GetPlayerName(args.id)} (${args.id})'s ${args.moneytype} to ${args.amount} [Previously: ${prevMoney}]`); 156 | return interaction.reply({ content: `${characterName} (${args.id})'s ${args.moneytype} has been set to ${player.Functions.GetMoney(args.moneytype)} (Previously: ${prevMoney})`, ephemeral: false }); 157 | } else { 158 | return interaction.reply({ content: "Something went wrong trying to set this player's money", ephemeral: false }); 159 | } 160 | } 161 | }, 162 | }; 163 | -------------------------------------------------------------------------------- /server/commands/qb-permissions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of zdiscord. 3 | * Copyright (C) 2021 Tony/zfbx 4 | * source: 5 | * 6 | * This work is licensed under the Creative Commons 7 | * Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ 9 | * or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 10 | */ 11 | 12 | module.exports = { 13 | name: "permissions", 14 | description: "Manage player's in-city permissions", 15 | role: "god", 16 | 17 | options: [ 18 | { 19 | type: "SUB_COMMAND", 20 | name: "add", 21 | description: "add a permission to a player", 22 | options: [ 23 | { 24 | name: "id", 25 | description: "Player's current id", 26 | required: true, 27 | type: "INTEGER", 28 | }, 29 | { 30 | name: "permission", 31 | description: "permission to give", 32 | required: true, 33 | type: "STRING", 34 | choices: [ 35 | { name: "admin", value: "admin" }, 36 | { name: "god", value: "god" }, 37 | ], 38 | }, 39 | ], 40 | }, 41 | { 42 | type: "SUB_COMMAND", 43 | name: "remove", 44 | description: "remove all permissions from a player", 45 | options: [ 46 | { 47 | name: "id", 48 | description: "Player's current id", 49 | required: true, 50 | type: "INTEGER", 51 | }, 52 | ], 53 | }, 54 | ], 55 | 56 | run: async (client, interaction, args) => { 57 | if (!GetPlayerName(args.id)) return interaction.reply({ content: "This ID seems invalid.", ephemeral: true }); 58 | if (args.add) { 59 | client.QBCore.Functions.AddPermission(args.id, args.permission); 60 | client.utils.log.info(`[${interaction.member.displayName}] Gave ${args.id} the ${args.permission} permission`); 61 | return interaction.reply({ content: `${GetPlayerName(args.id)} (${args.id}) was given ${args.permission} permission.`, ephemeral: false }); 62 | } else if (args.remove) { 63 | client.QBCore.Functions.RemovePermission(args.id); 64 | client.utils.log.info(`[${interaction.member.displayName}] Removed ${args.id} permissions`); 65 | return interaction.reply({ content: `${GetPlayerName(args.id)} (${args.id}) had their permissions removed.`, ephemeral: false }); 66 | } 67 | }, 68 | }; 69 | -------------------------------------------------------------------------------- /server/commands/qb-revive.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of zdiscord. 3 | * Copyright (C) 2021 Tony/zfbx 4 | * source: 5 | * 6 | * This work is licensed under the Creative Commons 7 | * Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ 9 | * or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 10 | */ 11 | 12 | module.exports = { 13 | name: "revive", 14 | description: "Raise a downed player to full health and stats", 15 | role: "admin", 16 | 17 | options: [ 18 | { 19 | name: "id", 20 | description: "Player's current id", 21 | required: true, 22 | type: "INTEGER", 23 | }, 24 | ], 25 | 26 | run: async (client, interaction, args) => { 27 | if (!GetPlayerName(args.id)) return interaction.reply({ content: "This ID seems invalid.", ephemeral: true }); 28 | emitNet("hospital:client:Revive", args.id); 29 | client.utils.log.info(`[${interaction.member.displayName}] revived ${GetPlayerName(args.id)} (${args.id})`); 30 | return interaction.reply({ content: `${GetPlayerName(args.id)} (${args.id}) has been fully healed.`, ephemeral: false }); 31 | }, 32 | }; 33 | -------------------------------------------------------------------------------- /server/commands/qb-reviveall.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of zdiscord. 3 | * Copyright (C) 2021 Tony/zfbx 4 | * source: 5 | * 6 | * This work is licensed under the Creative Commons 7 | * Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ 9 | * or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 10 | */ 11 | 12 | module.exports = { 13 | name: "revive-all", 14 | description: "Raise all downed players to full health and stats", 15 | role: "god", 16 | 17 | run: async (client, interaction, args) => { 18 | emitNet("hospital:client:Revive", -1); 19 | client.utils.log.info(`[${interaction.member.displayName}] revived EVERYONE`); 20 | return interaction.reply({ content: "Everyone has been fully healed.", ephemeral: false }); 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /server/commands/qb-time.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of zdiscord. 3 | * Copyright (C) 2021 Tony/zfbx 4 | * source: 5 | * 6 | * This work is licensed under the Creative Commons 7 | * Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ 9 | * or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 10 | */ 11 | 12 | module.exports = { 13 | name: "time", 14 | description: "set city time", 15 | role: "admin", 16 | 17 | options: [ 18 | { 19 | name: "hour", 20 | description: "what time to set to", 21 | required: true, 22 | type: "STRING", 23 | choices: [ 24 | { name: "1:00 AM", value: "1" }, 25 | { name: "2:00 AM", value: "2" }, 26 | { name: "3:00 AM", value: "3" }, 27 | { name: "4:00 AM", value: "4" }, 28 | { name: "5:00 AM", value: "5" }, 29 | { name: "6:00 AM", value: "6" }, 30 | { name: "7:00 AM", value: "7" }, 31 | { name: "8:00 AM", value: "8" }, 32 | { name: "9:00 AM", value: "9" }, 33 | { name: "10:00 AM", value: "10" }, 34 | { name: "11:00 AM", value: "11" }, 35 | { name: "12:00 PM", value: "12" }, 36 | { name: "1:00 PM", value: "13" }, 37 | { name: "2:00 PM", value: "14" }, 38 | { name: "3:00 PM", value: "15" }, 39 | { name: "4:00 PM", value: "16" }, 40 | { name: "5:00 PM", value: "17" }, 41 | { name: "6:00 PM", value: "18" }, 42 | { name: "7:00 PM", value: "19" }, 43 | { name: "8:00 PM", value: "20" }, 44 | { name: "9:00 PM", value: "21" }, 45 | { name: "10:00 PM", value: "22" }, 46 | { name: "11:00 PM", value: "23" }, 47 | { name: "12:00 AM", value: "24" }, 48 | ], 49 | }, 50 | ], 51 | 52 | run: async (client, interaction, args) => { 53 | if (GetResourceState("qb-weathersync") !== "started") return interaction.reply({ content: "This command requires QBCore's `qb-weathersync` to work", ephemeral: false }); 54 | // doesn't give any feedback to rely on :/ 55 | emit("qb-weathersync:server:setTime", args.hour, "0"); 56 | client.utils.log.info(`[${interaction.member.displayName}] set time to hour ${args.hour}`); 57 | return interaction.reply({ content: "Time has been set", ephemeral: false }); 58 | }, 59 | }; 60 | -------------------------------------------------------------------------------- /server/commands/qb-user-checkonline.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of zdiscord. 3 | * Copyright (C) 2021 Tony/zfbx 4 | * source: 5 | * 6 | * This work is licensed under the Creative Commons 7 | * Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ 9 | * or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 10 | */ 11 | 12 | module.exports = { 13 | name: "onlinecheck", 14 | type: "USER", 15 | role: "mod", 16 | 17 | run: async (client, interaction, args) => { 18 | const player = await client.utils.getPlayerFromDiscordId(interaction.targetId); 19 | if (!player) return interaction.reply({ content: `<@${interaction.targetId}> is offline right now.`, ephemeral: true }); 20 | const qbplayer = client.QBCore.Functions.GetPlayer(parseInt(player)); 21 | if (!qbplayer) return interaction.reply({ content: `<@${interaction.targetId}> is online but not loaded in.`, ephemeral: true }); 22 | return interaction.reply({ content: `<@${interaction.targetId}> is online! Playing as ${qbplayer.PlayerData.charinfo.firstname} ${qbplayer.PlayerData.charinfo.lastname} (${qbplayer.PlayerData.citizenid})`, ephemeral: true }); 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /server/commands/qb-vehicle.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of zdiscord. 3 | * Copyright (C) 2021 Tony/zfbx 4 | * source: 5 | * 6 | * This work is licensed under the Creative Commons 7 | * Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ 9 | * or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 10 | */ 11 | 12 | const useNotifyInsteadOfChat = false; 13 | const vehicleStates = { 14 | 0: "On the Streets", 15 | 1: "In Garage", 16 | 2: "In Police Impound", 17 | 3: "3", 18 | }; 19 | 20 | module.exports = { 21 | name: "vehicle", 22 | description: "Give user a vehicle with a fixed plate", 23 | role: "god", 24 | 25 | options: [ 26 | { 27 | type: "SUB_COMMAND", 28 | name: "give", 29 | description: "grant a car to a player", 30 | options: [ 31 | { 32 | name: "id", 33 | description: "Player's current id", 34 | required: true, 35 | type: "INTEGER", 36 | }, 37 | { 38 | name: "model", 39 | description: "Vehicle's spawn hash (ex: t20)", 40 | required: true, 41 | type: "STRING", 42 | }, 43 | { 44 | name: "plate", 45 | description: "Plate to give vehicle to player (max 8 characters)", 46 | required: false, 47 | type: "STRING", 48 | }, 49 | ], 50 | }, 51 | { 52 | type: "SUB_COMMAND", 53 | name: "lookup", 54 | description: "lookup vehicle by plate", 55 | options: [ 56 | { 57 | name: "plate", 58 | description: "Plate to look for", 59 | required: true, 60 | type: "STRING", 61 | }, 62 | ], 63 | }, 64 | ], 65 | 66 | run: async (client, interaction, args) => { 67 | 68 | if (args.give) { 69 | if (!GetPlayerName(args.id)) return interaction.reply({ content: "This ID seems invalid.", ephemeral: true }); 70 | const player = client.QBCore.Functions.GetPlayer(args.id); 71 | const vehicles = client.QBCore.Shared.Vehicles; 72 | const vehicle = vehicles[Object.keys(vehicles).find(key => key.toLowerCase() === args.model.toLowerCase())]; 73 | if (!vehicle) return interaction.reply({ content: `Vehicle model \`${args.model}\` does not exist.`, ephemeral: true }); 74 | 75 | const plate = args.plate ? args.plate.toUpperCase() : await createPlate(); 76 | if (plate.length > 8) return interaction.reply({ content: "Plate names can only be a max of 8 characters.", ephemeral: true }); 77 | const exists = await getVehicleByPlate(plate); 78 | if (exists.length > 0) return interaction.reply({ content: "The plate provided is already in use by another vehicle", ephemeral: true }); 79 | 80 | const save = await global.exports.oxmysql.insert_async("INSERT INTO player_vehicles (license, citizenid, vehicle, hash, mods, plate, state, garage) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", [ 81 | player.PlayerData.license, player.PlayerData.citizenid, vehicle.model, vehicle.hash, "{}", plate, 1, "pillboxgarage", 82 | ]); 83 | if (!save) return interaction.reply({ content: "Something went wrong saving the vehicle", ephemeral: true }); 84 | 85 | client.utils.log.info(`[${interaction.member.displayName}] Gave ${GetPlayerName(args.id)} (${args.id}) a ${args.model} with the plate ${plate}`); 86 | const playerMessage = `${vehicle.name} has been added to your garage at legion (Plate: ${plate})`; 87 | 88 | if (useNotifyInsteadOfChat) emitNet("QBCore:Notify", args.id, playerMessage); 89 | else client.utils.chatMessage(args.id, "Government", playerMessage, { color: [65, 105, 225] }); 90 | 91 | return interaction.reply({ content: `${GetPlayerName(args.id)} (${args.id}) was given a ${vehicle.name}. Plate: ${plate}`, ephemeral: false }); 92 | } 93 | else if (args.lookup) { 94 | let vehicle = await getVehicleByPlate(args.plate); 95 | if (vehicle.length < 1) return interaction.reply({ content: "Vehicle with this plate doesn't exist", ephemeral: true }); 96 | vehicle = vehicle[0]; 97 | const embed = new client.Embed(); 98 | embed.setDescription(`**Plate:** ${vehicle.plate} 99 | **Owner ID:** ${vehicle.citizenid} 100 | **Vehicle:** ${vehicle.vehicle} 101 | **Garage:** ${vehicle.garage} 102 | **State:** ${vehicleStates[vehicle.state] ?? "Recently Purchased?"} 103 | **Fuel:** ${vehicle.fuel}/100 104 | **Body:** ${vehicle.body}/1000 105 | **Status:** ${vehicle.status ?? "Null"} 106 | **Odometer:** ${vehicle.drivingdistance ?? 0} 107 | 108 | **Finacing Details:** 109 | **Balance:** $${vehicle.balance} 110 | **Payment Amount:** $${vehicle.paymentamount} 111 | **Payments Left:** ${vehicle.paymentsleft} 112 | **Finance Time:** ${vehicle.financetime}`); 113 | return interaction.reply({ embeds: [ embed ], ephemeral: false }); 114 | } 115 | }, 116 | }; 117 | 118 | async function getVehicleByPlate(plate) { 119 | return await global.exports.oxmysql.query_async("SELECT * FROM player_vehicles WHERE plate = ?", [plate]); 120 | } 121 | 122 | async function createPlate() { 123 | let plate = generatePlate(); 124 | let taken = true; 125 | while (taken) { 126 | const exists = await getVehicleByPlate(plate); 127 | if (exists.length > 0) plate = generatePlate(); 128 | else taken = false; 129 | } 130 | return plate; 131 | } 132 | 133 | function generatePlate() { 134 | return `${random(1, false)}${random(2)}${random(3, false)}${random(2)}`; 135 | } 136 | 137 | const random = (length = 8, alphabetical = true) => { 138 | const chars = alphabetical ? "ABCDEFGHIJKLMNOPQRSTUVWXYZ" : "0123456789"; 139 | let str = ""; 140 | for (let i = 0; i < length; i++) { 141 | str += chars.charAt(Math.floor(Math.random() * chars.length)); 142 | } 143 | return str; 144 | }; 145 | -------------------------------------------------------------------------------- /server/commands/qb-weather.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of zdiscord. 3 | * Copyright (C) 2021 Tony/zfbx 4 | * source: 5 | * 6 | * This work is licensed under the Creative Commons 7 | * Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ 9 | * or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 10 | */ 11 | 12 | module.exports = { 13 | name: "weather", 14 | description: "Manage city weather", 15 | role: "admin", 16 | 17 | options: [ 18 | { 19 | type: "SUB_COMMAND", 20 | name: "set", 21 | description: "set the weather to a preset", 22 | options: [ 23 | { 24 | name: "weather", 25 | description: "available weather presets", 26 | required: true, 27 | type: "STRING", 28 | choices: [ 29 | { name: "Extra Sunny", value: "EXTRASUNNY" }, 30 | { name: "Clear Sky", value: "CLEAR" }, 31 | { name: "Neutral", value: "NEUTRAL" }, 32 | { name: "Smog", value: "SMOG" }, 33 | { name: "Foggy", value: "FOGGY" }, 34 | { name: "Overcast", value: "OVERCAST" }, 35 | { name: "Cloudy", value: "CLOUDS" }, 36 | { name: "Clearing", value: "CLEARING" }, 37 | { name: "Rain", value: "RAIN" }, 38 | { name: "Thunder", value: "THUNDER" }, 39 | { name: "Snow", value: "SNOW" }, 40 | { name: "Snow (Light)", value: "SNOWLIGHT" }, 41 | { name: "Snow Blizzard", value: "BLIZZARD" }, 42 | { name: "Christmas Theme", value: "XMAS" }, 43 | { name: "Halloween Theme", value: "HALLOWEEN" }, 44 | ], 45 | }, 46 | ], 47 | }, 48 | { 49 | type: "SUB_COMMAND", 50 | name: "blackout", 51 | description: "toggle blackout", 52 | }, 53 | ], 54 | 55 | run: async (client, interaction, args) => { 56 | if (GetResourceState("qb-weathersync") !== "started") return interaction.reply({ content: "This command requires QBCore's `qb-weathersync` to work", ephemeral: false }); 57 | if (args.blackout) { 58 | // doesn't give any option for true or false or feedback to which was done -.- 59 | emit("qb-weathersync:server:toggleBlackout"); 60 | client.utils.log.info(`[${interaction.member.displayName}] toggled blackout`); 61 | return interaction.reply({ content: "Blackout has been toggled", ephemeral: false }); 62 | } else if (args.set) { 63 | // also doesn't give any feedback on it's success or failure -.- 64 | emit("qb-weathersync:server:setWeather", args.weather); 65 | client.utils.log.info(`[${interaction.member.displayName}] toggled weather to ${args.weather}`); 66 | return interaction.reply({ content: "Weather was updated", ephemeral: false }); 67 | } 68 | }, 69 | }; 70 | -------------------------------------------------------------------------------- /server/commands/resource.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of zdiscord. 3 | * Copyright (C) 2021 Tony/zfbx 4 | * source: 5 | * 6 | * This work is licensed under the Creative Commons 7 | * Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ 9 | * or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 10 | */ 11 | 12 | const { MessageButton } = require("discord.js"); 13 | 14 | module.exports = { 15 | name: "resource", 16 | description: "Manage server resources / scripts", 17 | role: "god", 18 | 19 | options: [ 20 | { 21 | type: "SUB_COMMAND", 22 | name: "start", 23 | description: "start a resource / script", 24 | options: [ 25 | { 26 | name: "script", 27 | description: "resource / script to start", 28 | required: true, 29 | type: "STRING", 30 | }, 31 | ], 32 | }, 33 | { 34 | type: "SUB_COMMAND", 35 | name: "ensure", 36 | description: "start/restart a resource / script", 37 | options: [ 38 | { 39 | name: "script", 40 | description: "resource / script to start or restart", 41 | required: true, 42 | type: "STRING", 43 | }, 44 | ], 45 | }, 46 | { 47 | type: "SUB_COMMAND", 48 | name: "stop", 49 | description: "stop a resource / script", 50 | options: [ 51 | { 52 | name: "script", 53 | description: "resource / script to start or restart", 54 | required: true, 55 | type: "STRING", 56 | }, 57 | ], 58 | }, 59 | { 60 | type: "SUB_COMMAND", 61 | name: "inspect", 62 | description: "Inspect the status of a resource / script", 63 | options: [ 64 | { 65 | name: "script", 66 | description: "resource / script to start or restart", 67 | required: true, 68 | type: "STRING", 69 | }, 70 | ], 71 | }, 72 | { 73 | type: "SUB_COMMAND", 74 | name: "refresh", 75 | description: "refreshes resources", 76 | }, 77 | { 78 | type: "SUB_COMMAND", 79 | name: "list", 80 | description: "list resources", 81 | }, 82 | ], 83 | 84 | run: async (client, interaction, args) => { 85 | if (!(args.refresh || args.inspect) && args.script == GetCurrentResourceName()) return interaction.reply({ content: "You can't restart this script", ephemeral: true }); 86 | if (!(args.refresh || args.inspect) && args.script == "qb-core") return interaction.reply({ content: "You should never restart this script as it will break your server", ephemeral: true }); 87 | await interaction.deferReply(); 88 | if (args.refresh) { 89 | ExecuteCommand("refresh"); 90 | return interaction.editReply({ content: "Resources have been refreshed", ephemeral: false }); 91 | } else if (args.inspect) { 92 | const reply = GetResourceState(args.script); 93 | return interaction.editReply({ content: `${args.script} is currently: ${reply}`, ephemeral: false }); 94 | } else if (args.stop) { 95 | const reply = StopResource(args.script); 96 | if (reply) return interaction.editReply({ content: `${args.script} has been stopped`, ephemeral: false }); 97 | else return interaction.editReply({ content: `${args.script} FAILED to be stopped or didn't exist`, ephemeral: false }); 98 | } else if (args.start) { 99 | const reply = StartResource(args.script); 100 | if (reply) return interaction.editReply({ content: `${args.script} was started if it wasn't already`, ephemeral: false }); 101 | else return interaction.editReply({ content: `${args.script} FAILED to be started and might not exist`, ephemeral: false }); 102 | } else if (args.ensure) { 103 | StopResource(args.script); 104 | const reply = StartResource(args.script); 105 | if (reply) return interaction.editReply({ content: `${args.script} has been started/restarted`, ephemeral: false }); 106 | else return interaction.editReply({ content: `${args.script} FAILED to be started/restarted and might not exist`, ephemeral: false }); 107 | } else if (args.list) { 108 | const r = []; 109 | for (let i = 0; i < GetNumResources(); i++) { 110 | const res = GetResourceByFindIndex(i); 111 | if (res && res !== "_cfx_internal" && GetResourceState(res) == "started") { 112 | r.push(res); 113 | } 114 | } 115 | const parts = []; 116 | r.sort((a, b) => a.localeCompare(b, undefined, { sensitivity: "base" })).forEach((res, index) => { 117 | const i = Math.floor(index / 20); 118 | if (!parts[i]) parts[i] = ""; 119 | parts[i] += `${res}\n`; 120 | }); 121 | const pages = []; 122 | parts.forEach((part) => { 123 | const embed = new client.Embed() 124 | .setTitle(`Resources (${GetNumResources()})`) 125 | .setDescription(`${part}`); 126 | pages.push(embed); 127 | }); 128 | const backBtn = new MessageButton().setCustomId("previousbtn").setEmoji("🔺").setStyle("SECONDARY"); 129 | const forwardBtn = new MessageButton().setCustomId("nextbtn").setEmoji("🔻").setStyle("SECONDARY"); 130 | client.paginationEmbed(interaction, pages, [backBtn, forwardBtn]); 131 | } 132 | }, 133 | }; 134 | -------------------------------------------------------------------------------- /server/commands/screenshot.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of zdiscord. 3 | * Copyright (C) 2021 Tony/zfbx 4 | * source: 5 | * 6 | * This work is licensed under the Creative Commons 7 | * Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ 9 | * or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 10 | */ 11 | 12 | const fs = require("fs").promises; 13 | const Buffer = require("buffer").Buffer; 14 | 15 | module.exports = { 16 | name: "screenshot", 17 | description: "Screenshot player's POV", 18 | role: "god", 19 | 20 | options: [ 21 | { 22 | name: "id", 23 | description: "Player's current id", 24 | required: true, 25 | type: "INTEGER", 26 | }, 27 | ], 28 | 29 | run: async (client, interaction, args) => { 30 | if (!GetPlayerName(args.id)) return interaction.reply({ content: "This ID seems invalid.", ephemeral: true }); 31 | if (GetResourceState("screenshot-basic") !== "started") return interaction.reply({ content: "This command requires citizenfx's `screenshot-basic` to work", ephemeral: false }); 32 | await interaction.reply("Taking screenshot.."); 33 | const name = `${client.utils.log.timestamp(true)}_${args.id}.jpg`; 34 | const data = await takeScreenshot(args.id).catch(error => { 35 | client.utils.log.error(error); 36 | return interaction.editReply("**Error requesting screenshot**"); 37 | }); 38 | const buffer = new Buffer.from(data, "base64"); 39 | const embed = new client.Embed() 40 | .setTitle(`${GetPlayerName(args.id)}'s Screen`) 41 | .setImage(`attachment://${name}`) 42 | .setFooter({ text: `Taken At ${client.utils.log.timestamp()}` }); 43 | await interaction.editReply({ content: null, embeds: [ embed ], files: [ { attachment: buffer, name: name } ] }).catch(console.error); 44 | if (client.config.SaveScreenshotsToServer) { 45 | await fs.mkdir(`${client.root}/screenshots`, { recursive: true }).catch(); 46 | await fs.writeFile(`${client.root}/screenshots/${name}`, data, { encoding: "base64", flag:"w+" }).catch(client.utils.log.error); 47 | } 48 | return client.utils.log.info(`[${interaction.member.displayName}] Took a screenshot of ${GetPlayerName(args.id)}'s (${args.id}) screen`); 49 | }, 50 | }; 51 | 52 | const takeScreenshot = async (id) => { 53 | return new Promise((resolve, reject) => { 54 | global.exports["screenshot-basic"]["requestClientScreenshot"](id, {}, async (error, data) => { 55 | if (error) return reject(error); 56 | resolve(data.split(";base64,").pop()); 57 | }); 58 | }); 59 | }; 60 | -------------------------------------------------------------------------------- /server/commands/server.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of zdiscord. 3 | * Copyright (C) 2021 Tony/zfbx 4 | * source: 5 | * 6 | * This work is licensed under the Creative Commons 7 | * Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ 9 | * or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 10 | */ 11 | 12 | module.exports = { 13 | name: "server", 14 | description: "Get FiveM and Discord Stats", 15 | 16 | run: async (client, interaction) => { 17 | if (client.isRolePresent(interaction.member, [client.config.DiscordModRoleId, client.config.DiscordAdminRoleId, client.config.DiscordGodRoleId])) { 18 | const embed = new client.Embed() 19 | .setThumbnail(interaction.guild.iconURL({ format: "png", size: 512 })) 20 | .addField("FiveM Server:", `**Version:** ${GetConvar("version", "Unknown")} 21 | **Server Name:** ${client.config.FiveMServerName} 22 | **Server IP:** ${client.config.FiveMServerIP} 23 | **Resource Count:** ${GetNumResources()} 24 | **Game Build:** ${GetConvar("sv_enforceGameBuild", "Unknown")} 25 | **Max Clients:** ${GetConvar("sv_maxClients", "Unknown")} 26 | **OneSync:** ${GetConvar("onesync_enabled", "Unknown")} 27 | **Uptime:** ${(GetGameTimer() / 1000 / 60).toFixed(2)} minutes 28 | **Online Players:** ${GetNumPlayerIndices()}`, false) 29 | .addField("Discord Server:", `**ID:** ${interaction.guildId} 30 | **Invite:** ${client.config.DiscordInviteLink} 31 | **Roles:** ${interaction.guild.roles.cache.size} 32 | **Channels:** ${interaction.guild.channels.cache.filter((chan) => chan.type === "GUILD_TEXT").size} 33 | **Members:** ${interaction.guild.memberCount}${getWhitelisted(client, interaction)} 34 | **Owner:** <@${interaction.guild.ownerId}> (${interaction.guild.ownerId})`, true) 35 | .setFooter({ text: "zdiscord by zfbx" }); 36 | return interaction.reply({ embeds: [ embed ] }); 37 | } else { 38 | const embed = new client.Embed() 39 | .setThumbnail(interaction.guild.iconURL({ format: "png", size: 512 })) 40 | .addField(client.config.FiveMServerName, `**Server IP:** ${client.config.FiveMServerIP} 41 | **Uptime:** ${(GetGameTimer() / 1000 / 60).toFixed(2)} minutes 42 | **Players:** ${GetNumPlayerIndices()}/${GetConvar("sv_maxClients", "Unknown")}`, false) 43 | .setFooter({ text: "zdiscord by zfbx" }); 44 | return interaction.reply({ embeds: [ embed ] }); 45 | } 46 | }, 47 | }; 48 | 49 | 50 | function getWhitelisted(client, interaction) { 51 | if (!client.config.EnableWhitelistChecking) return ""; 52 | const membersWithRole = interaction.guild.members.cache.filter(member => { 53 | let found = false; 54 | client.config.DiscordWhitelistRoleIds.forEach(role => { 55 | if (member.roles.cache.has(role)) found = true; 56 | }); 57 | return found; 58 | }); 59 | return `\n**Whitelisted:** ${membersWithRole.size}`; 60 | } 61 | -------------------------------------------------------------------------------- /server/commands/teleport.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of zdiscord. 3 | * Copyright (C) 2021 Tony/zfbx 4 | * source: 5 | * 6 | * This work is licensed under the Creative Commons 7 | * Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ 9 | * or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 10 | */ 11 | 12 | module.exports = { 13 | name: "teleport", 14 | description: "teleport a player", 15 | role: "mod", 16 | 17 | options: [ 18 | { 19 | type: "SUB_COMMAND", 20 | name: "coords", 21 | description: "teleport to specific coordinates", 22 | options: [ 23 | { 24 | name: "id", 25 | description: "Player's current id", 26 | required: true, 27 | type: "INTEGER", 28 | }, 29 | { 30 | name: "x", 31 | description: "x coordinate", 32 | required: true, 33 | type: "INTEGER", 34 | }, 35 | { 36 | name: "y", 37 | description: "y coordinate", 38 | required: true, 39 | type: "INTEGER", 40 | }, 41 | { 42 | name: "z", 43 | description: "z coordinate", 44 | required: true, 45 | type: "INTEGER", 46 | }, 47 | { 48 | name: "vehicle", 49 | description: "Teleport them with vehicle they're driving?", 50 | required: false, 51 | type: "BOOLEAN", 52 | }, 53 | ], 54 | }, 55 | { 56 | type: "SUB_COMMAND", 57 | name: "preset", 58 | description: "teleport to a pre-specified location", 59 | options: [ 60 | { 61 | name: "id", 62 | description: "Player's current id", 63 | required: true, 64 | type: "INTEGER", 65 | }, 66 | { 67 | name: "location", 68 | description: "location to teleport to", 69 | required: true, 70 | type: "STRING", 71 | choices: [ 72 | { name: "Airport", value: "airport" }, 73 | { name: "Maze Bank Roof", value: "mazeroof" }, 74 | { name: "Del Perro Pier", value: "pier" }, 75 | { name: "Fort Zancudo Base", value: "militarybase" }, 76 | { name: "Mount Chiliad", value: "chiliad" }, 77 | ], 78 | }, 79 | { 80 | name: "vehicle", 81 | description: "Teleport them with vehicle they're driving?", 82 | required: false, 83 | type: "BOOLEAN", 84 | }, 85 | ], 86 | }, 87 | ], 88 | 89 | run: async (client, interaction, args) => { 90 | const locations = { 91 | "airport": [ -1096.19, -3501.1, 17.18 ], 92 | "mazeroof": [ -75.57, -818.88, 327.96 ], 93 | "pier": [ -1712.06, -1136.48, 13.08 ], 94 | "militarybase": [ -2105.88, 2871.16, 32.81 ], 95 | "chiliad": [ 453.73, 5572.2, 781.18 ], 96 | }; 97 | if (!GetPlayerName(args.id)) return interaction.reply({ content: "This ID seems invalid.", ephemeral: true }); 98 | if (args.coords) { 99 | teleport(args.id, args.x, args.y, args.z, args.vehicle || false); 100 | client.utils.log.info(`[${interaction.member.displayName}] Teleported ${GetPlayerName(args.id)} (${args.id}) to ${args.x}, ${args.y}, ${args.z}`); 101 | return interaction.reply({ content: `${GetPlayerName(args.id)} (${args.id}) was teleported to specified coords.`, ephemeral: false }); 102 | } else if (args.preset) { 103 | teleport(args.id, locations[args.location][0], locations[args.location][1], locations[args.location][2], args.vehicle || false); 104 | client.utils.log.info(`[${interaction.member.displayName}] teleported ${args.id} to ${args.location}`); 105 | return interaction.reply({ content: `${GetPlayerName(args.id)} (${args.id}) was teleported to ${args.location}`, ephemeral: false }); 106 | } 107 | }, 108 | }; 109 | 110 | function teleport(id, x, y, z, withVehicle = false) { 111 | x = x.toFixed(2); 112 | y = y.toFixed(2); 113 | z = z.toFixed(2); 114 | if (NetworkGetEntityOwner(GetPlayerPed(id)) == id) { 115 | emitNet(`${GetCurrentResourceName()}:teleport`, id, x, y, z, withVehicle); 116 | } else { 117 | SetEntityCoords(GetPlayerPed(id), x, y, z); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /server/commands/teleportall.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of zdiscord. 3 | * Copyright (C) 2021 Tony/zfbx 4 | * source: 5 | * 6 | * This work is licensed under the Creative Commons 7 | * Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ 9 | * or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 10 | */ 11 | 12 | module.exports = { 13 | name: "teleport-all", 14 | description: "teleport everyone", 15 | role: "god", 16 | 17 | options: [ 18 | { 19 | type: "SUB_COMMAND", 20 | name: "coords", 21 | description: "teleport to specific coordinates", 22 | options: [ 23 | { 24 | name: "x", 25 | description: "x coordinate", 26 | required: true, 27 | type: "INTEGER", 28 | }, 29 | { 30 | name: "y", 31 | description: "y coordinate", 32 | required: true, 33 | type: "INTEGER", 34 | }, 35 | { 36 | name: "z", 37 | description: "z coordinate", 38 | required: true, 39 | type: "INTEGER", 40 | }, 41 | ], 42 | }, 43 | { 44 | type: "SUB_COMMAND", 45 | name: "preset", 46 | description: "teleport to a pre-specified location", 47 | options: [ 48 | { 49 | name: "location", 50 | description: "location to teleport to", 51 | required: true, 52 | type: "STRING", 53 | choices: [ 54 | { name: "Airport", value: "airport" }, 55 | { name: "Maze Bank Roof", value: "mazeroof" }, 56 | { name: "Del Perro Pier", value: "pier" }, 57 | { name: "Fort Zancudo Base", value: "militarybase" }, 58 | { name: "Mount Chiliad", value: "chiliad" }, 59 | ], 60 | }, 61 | ], 62 | }, 63 | ], 64 | 65 | run: async (client, interaction, args) => { 66 | const locations = { 67 | "airport": [ -1096.19, -3501.1, 17.18 ], 68 | "mazeroof": [ -75.57, -818.88, 327.96 ], 69 | "pier": [ -1712.06, -1136.48, 13.08 ], 70 | "militarybase": [ -2105.88, 2871.16, 32.81 ], 71 | "chiliad": [ 453.73, 5572.2, 781.18 ], 72 | }; 73 | if (args.coords) { 74 | teleportEveryone(args.x, args.y, args.z); 75 | client.utils.log.info(`[${interaction.member.displayName}] Teleported EVERYONE to ${args.x}, ${args.y}, ${args.z}`); 76 | return interaction.reply({ content: "Teleported everyone.", ephemeral: false }); 77 | } else if (args.preset) { 78 | teleportEveryone(locations[args.location][0], locations[args.location][1], locations[args.location][2]); 79 | client.utils.log.info(`[${interaction.member.displayName}] teleported EVERYONE to ${args.location}`); 80 | return interaction.reply({ content: `Everyone was teleported to ${args.location}`, ephemeral: false }); 81 | } 82 | }, 83 | }; 84 | 85 | function teleportEveryone(x, y, z) { 86 | x = x.toFixed(2); 87 | y = y.toFixed(2); 88 | z = z.toFixed(2); 89 | emitNet(`${GetCurrentResourceName()}:teleport`, -1, x, y, z, false); 90 | } 91 | -------------------------------------------------------------------------------- /server/commands/whitelist.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of zdiscord. 3 | * Copyright (C) 2021 Tony/zfbx 4 | * source: 5 | * 6 | * This work is licensed under the Creative Commons 7 | * Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ 9 | * or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 10 | */ 11 | 12 | module.exports = { 13 | name: "whitelist", 14 | description: "Manage whitelist", 15 | role: "god", 16 | 17 | options: [ 18 | { 19 | type: "SUB_COMMAND", 20 | name: "toggle", 21 | description: "Enable / Disable whitelisting", 22 | options: [ 23 | { 24 | name: "enabled", 25 | description: "Choose to enable or disable whitelist", 26 | required: true, 27 | type: "BOOLEAN", 28 | }, 29 | ], 30 | }, 31 | { 32 | type: "SUB_COMMAND", 33 | name: "addrole", 34 | description: "Temp add role to whitelist (restart reverts to config)", 35 | options: [ 36 | { 37 | name: "role", 38 | description: "role to whitelist", 39 | required: true, 40 | type: "ROLE", 41 | }, 42 | ], 43 | }, 44 | { 45 | type: "SUB_COMMAND", 46 | name: "removerole", 47 | description: "Temp remove role from whitelist (restart reverts to config)", 48 | options: [ 49 | { 50 | name: "role", 51 | description: "role to remove from whitelist", 52 | required: true, 53 | type: "ROLE", 54 | }, 55 | ], 56 | }, 57 | ], 58 | 59 | run: async (client, interaction, args) => { 60 | if (args.toggle) { 61 | const prev = client.config.EnableWhitelistChecking; 62 | client.config.EnableWhitelistChecking = args.enabled; 63 | return interaction.reply({ content: `Whitelist was previously ${prev ? "enabled" : "disabled"} and is now ${args.enabled ? "enabled" : "disabled"}`, ephemeral: true }); 64 | } else if (args.addrole) { 65 | if (client.config.DiscordWhitelistRoleIds.includes(args.role)) { 66 | return interaction.reply({ content: "That role is already whitelisted", ephemeral: true }); 67 | } 68 | client.config.DiscordWhitelistRoleIds.push(args.role); 69 | return interaction.reply({ content: "Role has been whitelisted till restart", ephemeral: true }); 70 | } else if (args.removerole) { 71 | if (!client.config.DiscordWhitelistRoleIds.includes(args.role)) { 72 | return interaction.reply({ content: "That role was not in the whitelist", ephemeral: true }); 73 | } 74 | client.config.DiscordWhitelistRoleIds = client.config.DiscordWhitelistRoleIds.filter(item => item !== args.role); 75 | return interaction.reply({ content: "Role has been removed from whitelist", ephemeral: true }); 76 | } 77 | 78 | }, 79 | }; 80 | -------------------------------------------------------------------------------- /server/events/guildBanAdd.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of zdiscord. 3 | * Copyright (C) 2021 Tony/zfbx 4 | * source: 5 | * 6 | * This work is licensed under the Creative Commons 7 | * Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ 9 | * or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 10 | */ 11 | 12 | module.exports = { 13 | name: "guildBanAdd", 14 | run: async (client, ban) => { 15 | // https://discord.js.org/#/docs/discord.js/stable/class/Client?scrollTo=e-guildBanAdd 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /server/events/guildBanRemove.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of zdiscord. 3 | * Copyright (C) 2021 Tony/zfbx 4 | * source: 5 | * 6 | * This work is licensed under the Creative Commons 7 | * Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ 9 | * or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 10 | */ 11 | 12 | module.exports = { 13 | name: "guildBanAdd", 14 | run: async (client, ban) => { 15 | // https://discord.js.org/#/docs/discord.js/stable/class/Client?scrollTo=e-guildBanRemove 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /server/events/guildMemberAdd.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of zdiscord. 3 | * Copyright (C) 2021 Tony/zfbx 4 | * source: 5 | * 6 | * This work is licensed under the Creative Commons 7 | * Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ 9 | * or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 10 | */ 11 | 12 | module.exports = { 13 | name: "guildMemberAdd", 14 | run: async (client, member) => { 15 | // https://discord.js.org/#/docs/discord.js/stable/class/Client?scrollTo=e-guildMemberAdd 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /server/events/guildMemberRemove.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of zdiscord. 3 | * Copyright (C) 2021 Tony/zfbx 4 | * source: 5 | * 6 | * This work is licensed under the Creative Commons 7 | * Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ 9 | * or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 10 | */ 11 | 12 | module.exports = { 13 | name: "guildMemberRemove", 14 | run: async (client, member) => { 15 | // https://discord.js.org/#/docs/discord.js/stable/class/Client?scrollTo=e-guildMemberRemove 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /server/events/guildMemberUpdate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of zdiscord. 3 | * Copyright (C) 2021 Tony/zfbx 4 | * source: 5 | * 6 | * This work is licensed under the Creative Commons 7 | * Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ 9 | * or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 10 | */ 11 | 12 | module.exports = { 13 | name: "guildMemberUpdate", 14 | run: async (client, oldMember, newMember) => { 15 | // https://discord.js.org/#/docs/discord.js/stable/class/Client?scrollTo=e-guildMemberUpdate 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /server/events/guildUpdate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of zdiscord. 3 | * Copyright (C) 2021 Tony/zfbx 4 | * source: 5 | * 6 | * This work is licensed under the Creative Commons 7 | * Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ 9 | * or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 10 | */ 11 | 12 | module.exports = { 13 | name: "guildUpdate", 14 | run: async (client, oldGuild, newGuild) => { 15 | // https://discord.js.org/#/docs/discord.js/stable/class/Client?scrollTo=e-guildUpdate 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /server/events/interactionCreate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of zdiscord. 3 | * Copyright (C) 2021 Tony/zfbx 4 | * source: 5 | * 6 | * This work is licensed under the Creative Commons 7 | * Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ 9 | * or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 10 | */ 11 | 12 | module.exports = { 13 | name: "interactionCreate", 14 | // interaction = https://discord.js.org/#/docs/main/stable/class/Interaction 15 | run: async (client, interaction) => { 16 | if (interaction.isCommand()) { 17 | const command = client.commands.get(interaction.commandName); 18 | if (!command) { 19 | return interaction.reply({ content: "An error has occurred ", ephemeral: true }).catch((error) => client.utils.log.handler("error", error)); 20 | } 21 | if (!client.hasPermission(interaction.member, command.role)) { 22 | return interaction.reply({ content: "You don't have permission to use this command", ephemeral: true }).catch(); 23 | } 24 | 25 | const args = {}; 26 | for (const option of interaction.options.data) { 27 | if (option.type === "SUB_COMMAND") { 28 | if (option.name) args[option.name] = true; 29 | option.options?.forEach((x) => { 30 | args[x.name] = x.value; 31 | }); 32 | } else if (option.value) { args[option.name] = option.value; } 33 | } 34 | interaction.member = interaction.guild.members.cache.get(interaction.user.id); 35 | try { 36 | command.run(client, interaction, args); 37 | } catch (error) { 38 | client.utils.log.error(error); 39 | interaction.reply({ content: "There was an error while executing this command!", ephemeral: true }).catch((err) => client.utils.log.handler("error", err)); 40 | } 41 | } 42 | if (interaction.isContextMenu()) { 43 | const command = client.commands.get(interaction.commandName); 44 | if (command) command.run(client, interaction); 45 | } 46 | }, 47 | }; 48 | -------------------------------------------------------------------------------- /server/events/inviteCreate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of zdiscord. 3 | * Copyright (C) 2021 Tony/zfbx 4 | * source: 5 | * 6 | * This work is licensed under the Creative Commons 7 | * Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ 9 | * or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 10 | */ 11 | 12 | module.exports = { 13 | name: "inviteCreate", 14 | run: async (client, invite) => { 15 | // https://discord.js.org/#/docs/discord.js/stable/class/Client?scrollTo=e-inviteCreate 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /server/events/inviteDelete.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of zdiscord. 3 | * Copyright (C) 2021 Tony/zfbx 4 | * source: 5 | * 6 | * This work is licensed under the Creative Commons 7 | * Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ 9 | * or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 10 | */ 11 | 12 | module.exports = { 13 | name: "inviteDelete", 14 | run: async (client, invite) => { 15 | // https://discord.js.org/#/docs/discord.js/stable/class/Client?scrollTo=e-inviteDelete 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /server/events/messageCreate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of zdiscord. 3 | * Copyright (C) 2021 Tony/zfbx 4 | * source: 5 | * 6 | * This work is licensed under the Creative Commons 7 | * Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ 9 | * or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 10 | */ 11 | 12 | module.exports = { 13 | name: "messageCreate", 14 | // msg = https://discord.js.org/#/docs/main/stable/class/Message 15 | run: async (client, msg) => { 16 | if (!msg.content || msg.author.bot) return; 17 | if (client.config.EnableStaffChatForwarding && msg.channel.id == client.config.DiscordStaffChannelId) { 18 | client.utils.sendStaffChatMessage(client.z, msg.member.displayName, msg.content); 19 | return client.z.utils.log.write(`${msg.member.displayName}: ${msg.content}`, { tag: "STAFFCHAT" }); 20 | } 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /server/events/messageDelete.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of zdiscord. 3 | * Copyright (C) 2021 Tony/zfbx 4 | * source: 5 | * 6 | * This work is licensed under the Creative Commons 7 | * Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ 9 | * or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 10 | */ 11 | 12 | module.exports = { 13 | name: "messageDelete", 14 | run: async (client, message) => { 15 | // https://discord.js.org/#/docs/discord.js/stable/class/Client?scrollTo=e-messageDelete 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /server/events/ready.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of zdiscord. 3 | * Copyright (C) 2021 Tony/zfbx 4 | * source: 5 | * 6 | * This work is licensed under the Creative Commons 7 | * Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ 9 | * or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 10 | */ 11 | 12 | module.exports = { 13 | name: "ready", 14 | once: true, 15 | run: async (client) => { 16 | if (client.config.EnableDiscordSlashCommands) { 17 | const guild = client.guilds.cache.get(client.config.DiscordGuildId); 18 | if (!guild) return client.utils.log.error("DISCORD SERVER NOT FOUND - Is your config for 'DiscordGuildId' set correctly?"); 19 | await guild.commands.set(client.arrayOfCommands).catch((error) => client.utils.log.handler("error", error)); 20 | } 21 | if (client.config.EnableBotStatusMessages && client.config.BotStatusMessages) statusUpdater(client); 22 | client.utils.log.info(`Logged in as ${client.user.tag}`); 23 | client.utils.log.info("Enjoying zdiscord? Consider supporting it at patreon.com/zfbx or paypal.me/zfbx <3"); 24 | emit("zdiscord:ready"); 25 | }, 26 | }; 27 | 28 | async function statusUpdater(client) { 29 | setInterval(function() { 30 | try { 31 | const msg = client.utils.replaceGlobals(client, client.config.BotStatusMessages[Math.floor(Math.random() * client.config.BotStatusMessages.length)]); 32 | client.user.setActivity({ name: msg, type: "PLAYING" }); 33 | } catch (e) { 34 | // Just gonna void these errors.. 35 | } 36 | }, 20000); 37 | } 38 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of zdiscord. 3 | * Copyright (C) 2021 Tony/zfbx 4 | * source: 5 | * 6 | * This work is licensed under the Creative Commons 7 | * Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ 9 | * or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 10 | */ 11 | 12 | const z = {}; 13 | 14 | const { readdirSync } = require("fs"); 15 | z.root = GetResourcePath(GetCurrentResourceName()); 16 | z.config = require(`${z.root}/config`); 17 | z.locale = require(`${z.root}/locales/${z.config.LanguageLocaleCode}`); 18 | z.utils = require(`${z.root}/server/utils`); 19 | 20 | try { 21 | z.QBCore = global.exports["qb-core"].GetCoreObject(); 22 | if (z.QBCore) z.utils.log.info("QBCore found! Supported QB commands will be loaded."); 23 | } catch { z.QBCore = false; } 24 | 25 | const Bot = require(`${z.root}/server/bot`); 26 | z.bot = new Bot(z); 27 | 28 | const addons = readdirSync(`${z.root}/server/addons`).filter(file => file.endsWith(".js")); 29 | for (const file of addons) { 30 | try { 31 | const Addon = require(`${z.root}/server/addons/${file}`); 32 | z[file.slice(0, -3)] = new Addon(z); 33 | z.utils.log.info(`[ADDON] ${file} addon found and loaded`); 34 | } catch (e) { 35 | z.utils.log.error(`[ADDON] ${file} errored and could not be loaded`); 36 | z.utils.log.error(e); 37 | } 38 | } 39 | 40 | 41 | SetConvarReplicated("zdiscord_servername", z.config.FiveMServerName); 42 | SetConvarReplicated("zdiscord_discordinvite", z.config.DiscordInviteLink); 43 | SetConvarReplicated("zdiscord_serverip", z.config.FiveMServerIP); 44 | SetConvarReplicated("zdiscord_userpresence", String(z.config.enableUserPresence)); 45 | 46 | on("playerConnecting", async (name, setKickReason, deferrals) => { 47 | const player = source; 48 | if (!z.config.EnableWhitelistChecking || !z.config.EnableDiscordBot) return; 49 | deferrals.defer(); 50 | await z.utils.sleep(0); 51 | deferrals.update(z.utils.replaceGlobals(z, z.locale.checkingWhitelist.replace(/{name}/g, name))); 52 | await z.utils.sleep(0); 53 | const discordID = z.utils.getPlayerDiscordId(player); 54 | if (!discordID) return deferrals.done(z.utils.replaceGlobals(z, z.locale.discordNotOpen)); 55 | const member = z.bot.getMember(discordID); 56 | if (!member) return deferrals.done(z.utils.replaceGlobals(z, z.locale.notInDiscordServer)); 57 | const whitelisted = z.bot.isRolePresent(member, z.config.DiscordWhitelistRoleIds); 58 | if (whitelisted) deferrals.done(); 59 | else deferrals.done(z.utils.replaceGlobals(z, z.locale.notWhitelisted)); 60 | }); 61 | 62 | 63 | on("playerJoining", (oldId) => { 64 | const source = global.source; 65 | if (!z.config.EnableDiscordBot) return; 66 | const member = z.bot.getMemberFromSource(source); 67 | if (z.config.EnableAutoAcePermissions) { 68 | for (const [group, role] of Object.entries(z.config.AutoAcePermissions)) { 69 | if (z.bot.isRolePresent(member, role)) { 70 | ExecuteCommand(`add_principal "player.${source}" "${group}"`); 71 | } 72 | } 73 | } 74 | if (z.bot.isRolePresent(member, z.config.StaffChatRoleIds)) { 75 | ExecuteCommand(`add_principal "player.${source}" group.zdiscordstaff`); 76 | } 77 | }); 78 | 79 | on("playerDropped", (reason) => { 80 | const source = global.source; 81 | if (!z.config.EnableDiscordBot) return false; 82 | if (z.config.EnableAutoAcePermissions) { 83 | for (const [group, role] of Object.entries(z.config.AutoAcePermissions)) { 84 | ExecuteCommand(`remove_principal "player.${source}" "${group}"`); 85 | } 86 | } 87 | }); 88 | 89 | if (z.config.EnableStaffChatForwarding) { 90 | RegisterCommand("staff", (source, args, raw) => { 91 | if (!IsPlayerAceAllowed(source, "zdiscord.staffchat")) return; 92 | z.utils.sendStaffChatMessage(z, GetPlayerName(source), raw.substring(6)); 93 | if (!z.config.EnableDiscordBot) return; 94 | const staffChannel = z.bot.channels.cache.get(z.config.DiscordStaffChannelId); 95 | if (!staffChannel) return z.utils.log.warn("DiscordStaffChannelId was not found, staff message not sent."); 96 | staffChannel.send({ content: `${GetPlayerName(source)}: ${raw.substring(6)}`, allowMentions: false }).catch((e) => { 97 | z.utils.log.error("I don't seem to have the required permissions to forward the staffchat to the configured staffchannel"); 98 | }); 99 | }, false); 100 | 101 | RegisterCommand("stafftoggle", (source, args, raw) => { 102 | if (IsPlayerAceAllowed(source, "zdiscord.staffchat")) { 103 | ExecuteCommand(`remove_principal "player.${source}" group.zdiscordstaff`); 104 | z.utils.chatMessage(source, z.locale.staffchat, "Staff chat disabled.", { color: [ 255, 255, 0 ] }); 105 | } else { 106 | const member = z.bot.getMemberFromSource(source); 107 | if (z.bot.isRolePresent(member, z.config.StaffChatRoleIds)) { 108 | ExecuteCommand(`add_principal "player.${source}" group.zdiscordstaff`); 109 | z.utils.chatMessage(source, z.locale.staffchat, "Staff chat enabled.", { color: [ 255, 255, 0 ] }); 110 | } 111 | } 112 | }, false); 113 | 114 | setImmediate(() => { 115 | emit("chat:addSuggestion", "/staff", "Send message to other staff (Staff only)", [ 116 | { name:"Message", help:"Message to send to other staff" }, 117 | ]); 118 | emit("chat:addSuggestion", "/stafftoggle", "Toggle staff chat messages", []); 119 | }); 120 | } 121 | 122 | // EXPORTS 123 | 124 | global.exports("isRolePresent", (identifier, role) => { 125 | return z.bot.isRolePresent(identifier, role); 126 | }); 127 | 128 | global.exports("getRoles", (identifier) => { 129 | return z.bot.getMemberRoles(identifier); 130 | }); 131 | 132 | global.exports("getName", (identifier) => { 133 | const member = z.bot.parseMember(identifier); 134 | return member.displayName || false; 135 | }); 136 | 137 | global.exports("getDiscordId", (identifier) => { 138 | return z.utils.getPlayerDiscordId(identifier); 139 | }); 140 | -------------------------------------------------------------------------------- /server/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of zdiscord. 3 | * Copyright (C) 2021 Tony/zfbx 4 | * source: 5 | * 6 | * This work is licensed under the Creative Commons 7 | * Attribution-NonCommercial-ShareAlike 4.0 International License. 8 | * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ 9 | * or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 10 | */ 11 | 12 | const util = require("util"); 13 | 14 | 15 | /** Get player identifiers as an object 16 | * @param {number} id - Player ID to get identifiers from 17 | * @returns {object} - object of returned identifiers */ 18 | const getPlayerIdentifiers = (id) => { 19 | const ids = {}; 20 | for (let i = 0; i < GetNumPlayerIdentifiers(id); i++) { 21 | const identifier = GetPlayerIdentifier(id, i).split(":"); 22 | ids[identifier[0]] = identifier[1]; 23 | } 24 | return ids; 25 | }; 26 | exports.getPlayerIdentifiers = getPlayerIdentifiers; 27 | 28 | 29 | /** Get player's discord id from source 30 | * @param {number} id - Player ID to get identifiers from 31 | * @returns {string|boolean} - discord id or false */ 32 | const getPlayerDiscordId = (id) => { 33 | const ids = getPlayerIdentifiers(id); 34 | return ids["discord"] || false; 35 | }; 36 | exports.getPlayerDiscordId = getPlayerDiscordId; 37 | 38 | 39 | /** Get player source from discord id 40 | * @param {string} discordid - Discord ID 41 | * @returns {string|boolean} - source or false */ 42 | const getPlayerFromDiscordId = async (discordid) => { 43 | let player = false; 44 | getPlayers().some(async function(p, i, a) { 45 | const id = getPlayerDiscordId(p); 46 | if (id == discordid) { 47 | player = p; 48 | return true; 49 | } 50 | return false; 51 | }); 52 | return player; 53 | }; 54 | exports.getPlayerFromDiscordId = getPlayerFromDiscordId; 55 | 56 | 57 | /** Await a set number of miliseconds as a promise for clean 1 line pauses 58 | * @param {number} ms - number of miliseconds to wait 59 | * @returns {Promise} - array of discord ids */ 60 | exports.sleep = (ms) => { 61 | return new Promise(resolve => setTimeout(resolve, ms)); 62 | }; 63 | 64 | 65 | /** Capitalize the first character of a string 66 | * @param {string} string - a word or sentence 67 | * @returns {string} - same sentence with first character uppercase */ 68 | exports.uppercaseFirstLetter = (string) => { 69 | return `${string[0].toUpperCase()}${string.slice(1)}` || ""; 70 | }; 71 | 72 | 73 | /** Replaces common global variables: {servername} {invite} {playercount} 74 | * @param {string} string - string to be converted 75 | * @return {string} - String with { variables } replaced */ 76 | exports.replaceGlobals = (z, string) => { 77 | return string 78 | .replace(/{servername}/g, z.config.FiveMServerName) 79 | .replace(/{invite}/g, z.config.DiscordInviteLink) 80 | .replace(/{playercount}/g, GetNumPlayerIndices()); 81 | }; 82 | 83 | 84 | /** Logging class for a cleaner console logging experience */ 85 | const log = { 86 | /** Returns a simple timestamp formatted as `YYYY-MM-DD HH:MM` 87 | * @returns {string} formatted timestamp of right now */ 88 | timestamp: (noSpaces = false) => { 89 | function pad(n) { return n < 10 ? "0" + n : n; } 90 | const date = new Date(); 91 | return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}${noSpaces ? "_" : " "}${pad(date.getHours())}${noSpaces ? "-" : ":"}${pad(date.getMinutes())}${noSpaces ? "-" : ":"}${pad(date.getSeconds())}`; 92 | }, 93 | 94 | /** Generic log without colors but still with timestamps 95 | * @param {string} content - Information to log to console 96 | * @param {object} settings - Optional overrides of style and label */ 97 | log: (content, { color = "\x1b[37m", tag = "LOG" } = {}) => { 98 | log.write(content, { color, tag }); 99 | }, 100 | 101 | /** Info message with cyan color in console with timestamps 102 | * @param {string} content - Information to log to console 103 | * @param {object} settings - Optional overrides of style and label */ 104 | info: (content, { color = "\x1b[1;36m", tag = "INF" } = {}) => { 105 | log.write(content, { color, tag }); 106 | }, 107 | 108 | /** Warning message with yellow color in console with timestamps 109 | * @param {string} content - Information to log to console 110 | * @param {object} settings - Optional overrides of style and label */ 111 | warn: (content, { color = "\x1b[33m", tag = "WRN" } = {}) => { 112 | log.write(content, { color, tag }); 113 | }, 114 | 115 | /** Error message with red color in console with timestamps 116 | * @param {string} content - Information to log to console 117 | * @param {object} settings - Optional overrides of style and label */ 118 | error: (content, { color = "\x1b[1;31m", tag = "ERR" } = {}) => { 119 | log.write(content, { color, tag, error: true }); 120 | return false; 121 | }, 122 | 123 | /** Write directly to the console with your own tag and style 124 | * @param {string} content - Information to log to console 125 | * @param {object} settings - Optional overrides of style and label */ 126 | write: (content, { color = "\x1b[37m", tag = "LOG", error = false } = {}) => { 127 | const stream = error ? process.stderr : process.stdout; 128 | stream.write(`\x1b[1;36m[zdiscord]\x1b[0m[${log.timestamp()}]${color}[${tag}]: ${log.clean(content)}\x1b[0m\n`); 129 | return false; 130 | }, 131 | 132 | /** Sanitize content for console logging 133 | * @param {string|object} item - Information to log to console 134 | * @returns {string} cleaned string of content provided */ 135 | clean: (item) => { 136 | if (typeof item === "string") return item; 137 | const cleaned = util.inspect(item, { depth: Infinity }); 138 | return cleaned; 139 | }, 140 | 141 | /** Special case log handler for zdiscord specifc logs 142 | * Or just outright void spammy logging (heartbeats) 143 | * @param {string|object} err - error to process */ 144 | handler: (type, err) => { 145 | const e = err.toString(); 146 | if (e.includes("[DISALLOWED_INTENTS]")) log.error("YOU DIDN'T ENABLE INTENTS - go back to the zdiscord readme.md and read the section under \"setup\""); 147 | else if (e.includes("[TOKEN_INVALID]")) log.error("YOUR DISCORD API TOKEN IS INVALID OR REVOKED - GENERATE A NEW ONE AND UPDATE THE CONFIG"); 148 | else if (e.includes("Missing Access")) log.error("NO COMMAND CREATION PERMISSIONS - You must reinvite the bot to your server with the invite link provided in setup"); 149 | else if (e.includes("[HeartbeatTimer]")) return; 150 | else if (e.includes("Heartbeat acknowledged")) return; 151 | else if (type === "error") log.error(e); 152 | else if (type === "warn") log.warn(e); 153 | else if (type === "info") log.info(e); 154 | else log.log(e); 155 | }, 156 | 157 | /** Special case error handler for zdiscord specifc errors 158 | * @param {boolean} statement - Statement to check (true = throw error) 159 | * @param {string} error - Message to throw if bool is true */ 160 | assert: (statement, error) => { 161 | if (statement == true) { 162 | log.error(error); 163 | } 164 | }, 165 | }; 166 | exports.log = log; 167 | 168 | 169 | /** Check if role or server id looks at least somewhat correct 170 | * @param {string} id - role id 171 | * @returns {boolean} - whether ID looks correct */ 172 | const isValidID = (id) => { 173 | return /^\d{17,21}$/.test(id); 174 | }; 175 | exports.isValidID = isValidID; 176 | 177 | 178 | /** send staff message to all staff in game with it enabled 179 | * @param {object} z - z 180 | * @param {string} name - Name the message is from 181 | * @param {string} msg - message to send */ 182 | const sendStaffChatMessage = (z, name, msg) => { 183 | if (!msg) return; 184 | getPlayers().forEach(async function(player, index, array) { 185 | if (IsPlayerAceAllowed(player, "zdiscord.staffchat")) { 186 | chatMessage(player, `[${z.locale.staffchat}] ${name}`, msg, { multiline: false, color: [ 255, 100, 0 ] }); 187 | } 188 | }); 189 | }; 190 | exports.sendStaffChatMessage = sendStaffChatMessage; 191 | 192 | 193 | /** send chat message 194 | * @param {number} destination - source id or -1 for all 195 | * @param {string} label - Name the message is from 196 | * @param {string} msg - message to send 197 | * @param {object} options - options: array color[r, g, b], bool multiline */ 198 | const chatMessage = (destination, label, msg, options) => { 199 | if (!options) { options = {}; } 200 | TriggerClientEvent("chat:addMessage", destination, { 201 | color: (options.color || [ 255, 255, 255 ]), 202 | multiline: options.multiline || false, 203 | args: [ label, msg ], 204 | }); 205 | }; 206 | exports.chatMessage = chatMessage; 207 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@discordjs/builders@^0.11.0": 6 | "integrity" "sha512-ZTB8yJdJKrKlq44dpWkNUrAtEJEq0gqpb7ASdv4vmq6/mZal5kOv312hQ56I/vxwMre+VIkoHquNUAfnTbiYtg==" 7 | "resolved" "https://registry.npmjs.org/@discordjs/builders/-/builders-0.11.0.tgz" 8 | "version" "0.11.0" 9 | dependencies: 10 | "@sindresorhus/is" "^4.2.0" 11 | "discord-api-types" "^0.26.0" 12 | "ts-mixer" "^6.0.0" 13 | "tslib" "^2.3.1" 14 | "zod" "^3.11.6" 15 | 16 | "@discordjs/collection@^0.4.0": 17 | "integrity" "sha512-zmjq+l/rV35kE6zRrwe8BHqV78JvIh2ybJeZavBi5NySjWXqN3hmmAKg7kYMMXSeiWtSsMoZ/+MQi0DiQWy2lw==" 18 | "resolved" "https://registry.npmjs.org/@discordjs/collection/-/collection-0.4.0.tgz" 19 | "version" "0.4.0" 20 | 21 | "@sapphire/async-queue@^1.1.9": 22 | "integrity" "sha512-CbXaGwwlEMq+l1TRu01FJCvySJ1CEFKFclHT48nIfNeZXaAAmmwwy7scUKmYHPUa3GhoMp6Qr1B3eAJux6XgOQ==" 23 | "resolved" "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.1.9.tgz" 24 | "version" "1.1.9" 25 | 26 | "@sindresorhus/is@^4.2.0": 27 | "integrity" "sha512-BrzrgtaqEre0qfvI8sMTaEvx+bayuhPmfe2rfeUGPPHYr/PLxCOqkOe4TQTDPb+qcqgNcsAtXV/Ew74mcDIE8w==" 28 | "resolved" "https://registry.npmjs.org/@sindresorhus/is/-/is-4.2.1.tgz" 29 | "version" "4.2.1" 30 | 31 | "@types/node-fetch@^2.5.12": 32 | "integrity" "sha512-MKgC4dlq4kKNa/mYrwpKfzQMB5X3ee5U6fSprkKpToBqBmX4nFZL9cW5jl6sWn+xpRJ7ypWh2yyqqr8UUCstSw==" 33 | "resolved" "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.12.tgz" 34 | "version" "2.5.12" 35 | dependencies: 36 | "@types/node" "*" 37 | "form-data" "^3.0.0" 38 | 39 | "@types/node@*": 40 | "integrity" "sha512-YofkM6fGv4gDJq78g4j0mMuGMkZVxZDgtU0JRdx6FgiJDG+0fY0GKVolOV8WqVmEhLCXkQRjwDdKyPxJp/uucg==" 41 | "resolved" "https://registry.npmjs.org/@types/node/-/node-17.0.8.tgz" 42 | "version" "17.0.8" 43 | 44 | "@types/ws@^8.2.2": 45 | "integrity" "sha512-NOn5eIcgWLOo6qW8AcuLZ7G8PycXu0xTxxkS6Q18VWFxgPUSOwV0pBj2a/4viNZVu25i7RIB7GttdkAIUUXOOg==" 46 | "resolved" "https://registry.npmjs.org/@types/ws/-/ws-8.2.2.tgz" 47 | "version" "8.2.2" 48 | dependencies: 49 | "@types/node" "*" 50 | 51 | "asynckit@^0.4.0": 52 | "integrity" "sha1-x57Zf380y48robyXkLzDZkdLS3k=" 53 | "resolved" "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" 54 | "version" "0.4.0" 55 | 56 | "combined-stream@^1.0.8": 57 | "integrity" "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==" 58 | "resolved" "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz" 59 | "version" "1.0.8" 60 | dependencies: 61 | "delayed-stream" "~1.0.0" 62 | 63 | "delayed-stream@~1.0.0": 64 | "integrity" "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" 65 | "resolved" "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" 66 | "version" "1.0.0" 67 | 68 | "discord-api-types@^0.26.0": 69 | "integrity" "sha512-T5PdMQ+Y1MEECYMV5wmyi9VEYPagEDEi4S0amgsszpWY0VB9JJ/hEvM6BgLhbdnKky4gfmZEXtEEtojN8ZKJQQ==" 70 | "resolved" "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.26.1.tgz" 71 | "version" "0.26.1" 72 | 73 | "discord.js@github:zfbx/discord.js": 74 | "integrity" "sha512-ma8BQG8KBKSTvJH6IOr69kSqGRBbWR8JlAiAKAyHWzMORCR8IQUteiKH0JvQeJ64Uzcy3408YXy0mSEb0OWGVA==" 75 | "resolved" "https://codeload.github.com/zfbx/discord.js/tar.gz/bb643b504bd1e4416389e81f4f0ff9ce3a111aa0" 76 | "version" "13.6.0" 77 | dependencies: 78 | "@discordjs/builders" "^0.11.0" 79 | "@discordjs/collection" "^0.4.0" 80 | "@sapphire/async-queue" "^1.1.9" 81 | "@types/node-fetch" "^2.5.12" 82 | "@types/ws" "^8.2.2" 83 | "discord-api-types" "^0.26.0" 84 | "form-data" "^4.0.0" 85 | "node-fetch" "^2.6.1" 86 | "ws" "^8.4.0" 87 | 88 | "form-data@^3.0.0": 89 | "integrity" "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==" 90 | "resolved" "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz" 91 | "version" "3.0.1" 92 | dependencies: 93 | "asynckit" "^0.4.0" 94 | "combined-stream" "^1.0.8" 95 | "mime-types" "^2.1.12" 96 | 97 | "form-data@^4.0.0": 98 | "integrity" "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==" 99 | "resolved" "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz" 100 | "version" "4.0.0" 101 | dependencies: 102 | "asynckit" "^0.4.0" 103 | "combined-stream" "^1.0.8" 104 | "mime-types" "^2.1.12" 105 | 106 | "mime-db@1.51.0": 107 | "integrity" "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==" 108 | "resolved" "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz" 109 | "version" "1.51.0" 110 | 111 | "mime-types@^2.1.12": 112 | "integrity" "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==" 113 | "resolved" "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz" 114 | "version" "2.1.34" 115 | dependencies: 116 | "mime-db" "1.51.0" 117 | 118 | "node-fetch@^2.6.1": 119 | "integrity" "sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==" 120 | "resolved" "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz" 121 | "version" "2.6.6" 122 | dependencies: 123 | "whatwg-url" "^5.0.0" 124 | 125 | "tr46@~0.0.3": 126 | "integrity" "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" 127 | "resolved" "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz" 128 | "version" "0.0.3" 129 | 130 | "ts-mixer@^6.0.0": 131 | "integrity" "sha512-nXIb1fvdY5CBSrDIblLn73NW0qRDk5yJ0Sk1qPBF560OdJfQp9jhl+0tzcY09OZ9U+6GpeoI9RjwoIKFIoB9MQ==" 132 | "resolved" "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.0.tgz" 133 | "version" "6.0.0" 134 | 135 | "tslib@^2.3.1": 136 | "integrity" "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" 137 | "resolved" "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz" 138 | "version" "2.3.1" 139 | 140 | "webidl-conversions@^3.0.0": 141 | "integrity" "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" 142 | "resolved" "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz" 143 | "version" "3.0.1" 144 | 145 | "whatwg-url@^5.0.0": 146 | "integrity" "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=" 147 | "resolved" "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz" 148 | "version" "5.0.0" 149 | dependencies: 150 | "tr46" "~0.0.3" 151 | "webidl-conversions" "^3.0.0" 152 | 153 | "ws@^8.4.0": 154 | "integrity" "sha512-Kbk4Nxyq7/ZWqr/tarI9yIt/+iNNFOjBXEWgTb4ydaNHBNGgvf2QHbS9fdfsndfjFlFwEd4Al+mw83YkaD10ZA==" 155 | "resolved" "https://registry.npmjs.org/ws/-/ws-8.4.2.tgz" 156 | "version" "8.4.2" 157 | 158 | "zod@^3.11.6": 159 | "integrity" "sha512-daZ80A81I3/9lIydI44motWe6n59kRBfNzTuS2bfzVh1nAXi667TOTWWtatxyG+fwgNUiagSj/CWZwRRbevJIg==" 160 | "resolved" "https://registry.npmjs.org/zod/-/zod-3.11.6.tgz" 161 | "version" "3.11.6" 162 | --------------------------------------------------------------------------------