# SLEEK REPORT SYSTEM

{% embed url="<https://youtu.be/_0kLBC7eBmY?si=y3zePifm9RySGZb7>" %}

{% hint style="success" %}
This script is compatible with **ESX and QB**
{% endhint %}

{% hint style="danger" %}
Its only dependency is <mark style="color:red;">**SCREENSHOT-BASIC**</mark>. Used for in-game screenshots added to reports. Download it here:&#x20;

<https://github.com/citizenfx/screenshot-basic>
{% endhint %}

**IMPORTANT NOTICE FIRST**

{% hint style="warning" %}
Our script uses a snippet of code programmed in <mark style="color:blue;">**C#**</mark> (It is a hybrid between .LUA and C#). This doesn't cause any issues on your server or with the script, but you need to keep a couple of things in mind:

1. You need to have the **latest artifacts** released by **FiveM (**[**https://runtime.fivem.net/artifacts/fivem/build\_server\_windows/master/**](https://runtime.fivem.net/artifacts/fivem/build_server_windows/master/)**)**
2. In the **SERVER** folder, you'll see many files, if you're used to only having one server.lua, it might seem strange. <mark style="color:blue;">**C#**</mark> works like this, they're normal files. Don't worry!
   {% endhint %}

**INSTALLATION GUIDE**

1. Download from [KEYMASTER ](https://keymaster.fivem.net/login?return_url=/asset-grants)and Unzip the **`forge-report.pack.zip`** and place this folder in your server's resource folder.
2. Add the resource to your server start config: **`ensure forge-report`**, the name of the folder must not be changed or the resource will not function correctly. <mark style="color:red;">It should be ensured below the</mark> <mark style="color:red;"></mark><mark style="color:red;">**screenshot-basic**</mark> <mark style="color:red;"></mark><mark style="color:red;">and the core. Something like this:</mark>

{% code lineNumbers="true" %}

```lua
ensure es_extended -- or qb-core
ensure screenshot-basic
ensure forge-report
-- The rest
```

{% endcode %}

3. Delete or remove from your resources folder any other **Report System** you have.
4. Clear the cache of your server and also of your own FiveM.
5. Reboot the entire server with the forge script well ensured in your server.cfg.

**CONFIG**

The following will explain all the settings, one of the most important things that I recommend you spend a few minutes to understand in order to offer your users the best possible experience.

{% tabs %}
{% tab title="CONFIG" %}
{% code lineNumbers="true" %}

```lua
Config = {}

--  ██████╗ ██████╗ ███╗   ██╗███████╗██╗ ██████╗ ██╗   ██╗██████╗  █████╗ ████████╗██╗ ██████╗ ███╗   ██╗
-- ██╔════╝██╔═══██╗████╗  ██║██╔════╝██║██╔════╝ ██║   ██║██╔══██╗██╔══██╗╚══██╔══╝██║██╔═══██╗████╗  ██║
-- ██║     ██║   ██║██╔██╗ ██║█████╗  ██║██║  ███╗██║   ██║██████╔╝███████║   ██║   ██║██║   ██║██╔██╗ ██║
-- ██║     ██║   ██║██║╚██╗██║██╔══╝  ██║██║   ██║██║   ██║██╔══██╗██╔══██║   ██║   ██║██║   ██║██║╚██╗██║
-- ╚██████╗╚██████╔╝██║ ╚████║██║     ██║╚██████╔╝╚██████╔╝██║  ██║██║  ██║   ██║   ██║╚██████╔╝██║ ╚████║
--  ╚═════╝ ╚═════╝ ╚═╝  ╚═══╝╚═╝     ╚═╝ ╚═════╝  ╚═════╝ ╚═╝  ╚═╝╚═╝  ╚═╝   ╚═╝   ╚═╝ ╚═════╝ ╚═╝  ╚═══╝

-- General Configuration:
Config.framework = "QB" -- Framework used: 'ESX', 'QB', or 'CUSTOM'
Config.sql = "OXMYSQL" -- OXMYSQL, MYSQL-ASYNC, GHMATTIMYSQL
Config.imageWebhook = "your_webhook" -- URL for the webhook to send images. Fill with your webhook URL

-- Command Configuration
Config.ReportCommand = 'report' -- Command for players to create a report
Config.AdminReportCommand = 'reports' -- Command for admins to view reports

-- Report Categories
Config.ReportCategories = {
    'Antirol', -- Example category
    'Other', -- Example category
    -- Add more categories as needed
}

-- Admin Role Configuration
Config.AdminRoles = {
    'god',
    'superadmin', -- Highest level of admin
    'admin',      -- Regular admin
    -- Add more roles as needed
}

-- Admin Options
Config.AdminOptions = {
    OnDutyToggle = true, -- Option for admins to toggle duty status
    SpectatePlayer = true, -- Allow admins to spectate players
    TeleportOptions = true, -- Teleport options (GOTO, bring, etc.)
}
Config.ExitSpectatorKey = 22 -- The key to stop spectating a person -> https://docs.fivem.net/docs/game-references/controls/

-- Player Data Collection
Config.PlayerDataCollection = {
    CollectHealthArmor = true, -- Collect health and armor data of the reporting player
    CollectNearbyPlayers = true, -- Collect data of players near the reporting player
    MaxDistance = 35, -- Max distance for collecting data of nearby players
}

-- Interface Configuration
Config.UI = {
    color = '#31afd4',
    translations = {
        ["SUBJECT"] = "SUBJECT",
        ["SUBJECT_PLACEHOLDER"] = "Clear title of your case...",
        ["INFORMATION"] = "INFORMATION",
        ["INFORMATION_PLACEHOLDER"] = "All the detailed information about your problem...",
        ["LINK"] = "link",
        ["LINK_PLACEHOLDER"] = "Video Link or Screenshot Link...",
        ["IMAGE"] = "IMAGE",
        ["SCREENSHOT"] = "Click to take screenshot of what you see",
        ["SEND"] = "SEND",
        ["ADMINSON"] = "ADMINS ON",
        ["DEFAULT_NOTICE"] = "If no admin is available, open a ticket in Discord",-- Default notice in the report UI
        ["YOUR_ID"] = "Your ID is:",
        ["REPORT_SYSTEM"] = "Report system",
        ["ADMINS_DUTY"] = "Admins duty",
        ["VIEW"] = "View",
        ["REPORT_HISTORY"] = "Report history",
        ["HEALTH"] = "Health",
        ["ARMOR"] = "Armor",
        ["PLAYERS_IN_AREA"] = "Players in area",
        ["SEND_TRANSCRIPTION"] = "Send chat transcription to the Discord LOG",
        ["WRITE_TO_ADMIN"] = "Write to the Admin...",
        ["WRITE_TO_USER"] = "Write to the Player...",
        ["SPECTATE"] = "SPECTATE",
        ["BRING"] = "BRING",
        ["GOTO"] = "GOTO",
        ["SOLVED"] = "SOLVED"
    },
}

-- The order in which the reports are arranged in the menu
Config.SortType = "statusDESC" --date/statusASC/statusDESC

-- Notifications and Webhook Configuration
Config.Notifications = {
    -- Webhook for report notifications
    ReportWebhookUrl = 'your_webhook', -- Webhook URL for report submissions and results

    -- Webhook for player information requests by admins
    PlayerInfoWebhookUrl = 'your_webhook', -- Webhook URL for sending player information

    -- Webhook for notifying when an administrator enters/exits duty
    AdminDutyWebhookUrl = 'your_webhook', -- Webhook URL for sending duty information

    NotifyAdminOnReport = true, -- Notify admins when a new report is submitted
    NotifyPlayerOnResponse = true, -- Notify players when their report is responded to
}

-- Configure the aesthetics and texts of the logs
Config.Webhook = {
    Color = 16753920,
    Title = "**Report**",
    Author = "Forge Reports",
    IconUrl = "https://ferko.pl/wp-content/uploads/2022/01/fivem-4.png"
}
Config.WebhookText = {
    report_received = '**New report received.**\n**Subject:** {subject}\n**Description:** {description}\n\n**ID:** {id}\n**Character:** {charname}\n**Identifier:** {identifier}\n**Discord:** <@!{discord}>\n**Steam:** [{steam}](https://steamcommunity.com/profiles/{steam})',
    report_concluded = '**ReportID:** {reportId} has been closed.',
    report_canceled = '**ReportID:** {reportId} has been canceled.',
    report_user_respond = '**User responed**\n\n**ReportID:** {reportId}\n**Message:** {message}\n\n**ID:** {id}\n**Character:** {charname}\n**Identifier:** {identifier}\n**Discord:** <@!{discord}>\n**Steam:** [{steam}](https://steamcommunity.com/profiles/{steam})',
    admin_on_duty = '**Admin has gone on duty**\n\n**ID:** {id}\n**Character:** {charname}\n**Identifier:** {identifier}\n**Discord:** <@!{discord}>\n**Steam:** [{steam}](https://steamcommunity.com/profiles/{steam})',
    admin_off_duty = '**Admin has gone off duty**\n\n**ID:** {id}\n**Character:** {charname}\n**Identifier:** {identifier}\n**Discord:** <@!{discord}>\n**Steam:** [{steam}](https://steamcommunity.com/profiles/{steam})',
}

-- Configure or translate the texts of the notifications
Config.NotificationsText = {
    report_received = 'Report received. An admin will review it shortly.',
    user_response = 'A user has responded to report.',
    admin_response = 'An admin has responded to your report.',
    report_submitted = 'Your report has been submitted successfully.',
    report_concluded = 'Your report has been concluded.',
    report_canceled = 'Your report has been canceled.',
    new_report = 'A new report has been submitted.',
    player_offline = 'This Player is offline.'
}
Config.SpectateText = "Spectating: press [SPACE] to exit"
```

{% endcode %}
{% endtab %}

{% tab title="SQL" %}
{% code lineNumbers="true" fullWidth="true" %}

```sql
CREATE TABLE IF NOT EXISTS `forge_report` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `date_creation` datetime NOT NULL DEFAULT current_timestamp(),
  `date_updated` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
  `reporter_identifier` varchar(48) NOT NULL,
  `reporter_steam_id` varchar(50) NOT NULL,
  `reporter_discord_id` varchar(18) DEFAULT NULL,
  `details` text NOT NULL DEFAULT '',
  `chat` longtext NOT NULL DEFAULT '',
  `state` enum('Completed','Pending','In Progress','Canceled') NOT NULL DEFAULT 'Pending',
  `discord_ticket_opened` bit(1) NOT NULL DEFAULT b'0',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;
```

{% endcode %}
{% endtab %}

{% tab title="SERVER OPEN FUNCTIONS" %}

```lua
function Notification(playerId, message)
    TriggerClientEvent('chat:addMessage', playerId, message)
end

function GetPlayerIdFromIdentifier(identifier)
    local playerId = 0
    if Config.framework == "ESX" then
        local xPlayer = ESX.GetPlayerFromIdentifier(identifier)
        if xPlayer then
            for k, v in pairs(GetPlayers()) do if ESX.GetPlayerFromId(v).getIdentifier() == identifier then playerId = tonumber(v) break end end
        end
    elseif Config.framework == "QB" then
        --local xPlayer = QBCore.Functions.GetSource(identifier)
        --if xPlayer then
        for k, v in pairs(GetPlayers()) do if QBCore.Functions.GetPlayer(tonumber(v)).PlayerData.citizenid == identifier then playerId = tonumber(v) break end end
       -- end
    end

    return playerId
end

function GetPlayerIdentifer(source)
    local identifier = ""
    if Config.framework == "ESX" then
        local xPlayer = ESX.GetPlayerFromId(source)
        identifier = xPlayer.getIdentifier()
    elseif Config.framework == "QB" then
        local xPlayer = QBCore.Functions.GetPlayer(source)
        identifier = xPlayer.PlayerData.citizenid
    end

    return identifier
end

function GetCharacterName(source)
    local name = ""
    if Config.framework == "ESX" then
        local xPlayer = ESX.GetPlayerFromId(source)
        name = xPlayer.getName()
    elseif Config.framework == "QB" then
        local xPlayer = QBCore.Functions.GetPlayer(source)
        name = xPlayer.PlayerData.charinfo.firstname.." "..xPlayer.PlayerData.charinfo.lastname
    end

    return name
end

function GetOfflineCharacterName(identifier)
    local name = ""
    if Config.framework == "ESX" then
        local response = SqlFunc("fetchAll", 'SELECT firstname, lastname FROM users WHERE identifier = @identifier', { ['@identifier'] = identifier })
        name = response[1].firstname.." "..response[1].lastname
    elseif Config.framework == "QB" then
        local response = SqlFunc("fetchAll", 'SELECT charinfo FROM players WHERE citizenid = @identifier', { ['@identifier'] = identifier })
        data = json.decode(response[1].charinfo)
        name = data.firstname.." "..data.lastname
    end

    return name
end

function CheckIfAdmin(source, ranks)
    if Config.framework == "ESX" then
        local esxPlayer = ESX.GetPlayerFromId(source)
        if ranks then
            for key, rank in pairs(ranks) do
                if esxPlayer.group == rank then
                    return true
                end
            end
        else
            return esxPlayer.group == 'admin' or esxPlayer.group == 'superadmin'
        end
    elseif Config.framework == "QB" then
        if ranks then
            for key, rank in pairs(ranks) do
                if QBCore.Functions.HasPermission(source, rank) then
                    return true
                end
            end
        else
            return QBCore.Functions.HasPermission(source, 'admin') or QBCore.Functions.HasPermission(source, 'god')
        end
    end
en
```

{% endtab %}

{% tab title="CLIENT OPEN FUNCTIONS" %}

```lua
function Notification(message)
    TriggerEvent('chat:addMessage', message)
end
```

{% endtab %}
{% endtabs %}

{% hint style="success" %}
**If you want to edit the aesthetics or design. You have the HTML open so you can modify the style and everything as you want.**

The script is **RESPONSIVE** for all resolutions as well.
{% endhint %}
