# 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 %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://codeforge.gitbook.io/codeforge/sleek-series/sleek-report-system.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
