Fivem Discord Log Botu - QBCore Detaylı ve Kolay Anlatım

ff

ExxDev

TRIAL MOD.
Katılım: 18 Tem 2026
Mesajlar: 4
Beğeniler: 2

FIVEM DISCORD LOG SİSTEMİ

QBCore için giriş-çıkış, para, ölüm, item, araç ve yönetici logları

Sıfırdan başlayanlar için adım adım anlatım

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Bu Rehberde Ne Yapacağız?

Bu rehberde QBCore tabanlı FiveM sunucumuzda gerçekleşen önemli işlemleri Discord kanallarına gönderen bir log sistemi hazırlayacağız.

Hazırlayacağımız sistem:

  • Oyuncu giriş logu
  • Oyuncu çıkış logu
  • Citizen ID ve Rockstar License logu
  • Para ekleme ve çıkarma logu
  • Oyuncu ölüm ve öldürme logu
  • Item verme ve alma logu
  • Araç satın alma logu
  • Yönetici işlem logu
  • Özel script logları
  • Discord embed mesajları
  • Log sırası ve spam koruması

Bilgilendirme: Sadece log göndermek için gerçek bir Discord botu oluşturmak zorunda değiliz. Discord webhook sistemi bizim için bot görünümünde mesaj gönderebilir.

Slash komutları, rol verme, mesaj okuma veya Discord üzerinden FiveM sunucusunu yönetme istiyorsanız ayrıca gerçek bir Discord botu gerekir. Bu rehber yalnızca güvenli sunucu log sistemi içindir.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

1. Discord Log Kanallarını Oluşturma

Discord sunucunuzda öncelikle yalnızca yönetim ekibinin görebileceği bir kategori oluşturun:

Kod:
📁 SUNUCU LOGLARI

Bu kategorinin içerisine aşağıdaki kanalları açabilirsiniz:

Kod:
📥・giriş-çıkış-log
💰・para-log
💀・ölüm-log
📦・item-log
🚗・araç-log
🛡️・admin-log
⚙️・genel-log

Kategori izinlerinde @everyone rolünün “Kanalı Görüntüle” yetkisini kapatın.

Yalnızca kurucu, geliştirici ve gerekli yönetim rollerine görüntüleme izni verin.

Güvenlik uyarısı: Log kanallarında Citizen ID, Rockstar License ve Discord ID gibi özel bilgiler bulunabilir. Bu kanalları normal oyunculara açmayın.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

2. Discord Webhook Oluşturma

Webhook oluşturmak istediğiniz log kanalına girin.

Ardından:

  1. Kanalın yanındaki dişli simgesine basın.
  2. Entegrasyonlar bölümüne girin.
  3. Web Kancaları / Webhooks seçeneğini açın.
  4. Yeni Webhook butonuna basın.
  5. Webhook adını “ExxDev FiveM Logs” yapın.
  6. Mesajların gönderileceği kanalı seçin.
  7. Webhook URL’sini Kopyala butonuna basın.

Örnek bir webhook adresi şu yapıya benzer:

Kod:
https://discord.com/api/webhooks/WEBHOOK_ID/WEBHOOK_TOKEN

Her kanal için ayrı webhook oluşturabilirsiniz.

Webhook adresinizi kimseyle paylaşmayın.

Webhook adresini bilen bir kişi Discord kanalınıza istediği mesajı gönderebilir. Webhook adresiniz sızarsa eski webhooku silin ve yenisini oluşturun.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

3. FiveM Resource Klasörünü Oluşturma

FiveM sunucunuzun resources klasörüne girin:

Kod:
resources/[standalone]

Burada şu isimde yeni bir klasör oluşturun:

Kod:
exxdev_discordlogs

Klasör yapısı aşağıdaki gibi olmalıdır:

Kod:
exxdev_discordlogs/
├── fxmanifest.lua
├── config.lua
└── server/
    └── main.lua

Dosya uzantılarının yanlışlıkla .txt olmadığından emin olun.

Yanlış:

Kod:
fxmanifest.lua.txt
config.lua.txt
main.lua.txt

Doğru:

Kod:
fxmanifest.lua
config.lua
main.lua

Windows kullanıyorsanız Dosya Gezgini üzerinden Görünüm > Dosya adı uzantıları seçeneğini açabilirsiniz.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

4. fxmanifest.lua Dosyası

fxmanifest.lua dosyasını açın ve aşağıdaki kodu içerisine yapıştırın:

Kod:
fx_version 'cerulean'
game 'gta5'

author 'ExxDev'
description 'QBCore Discord Log Sistemi'
version '1.0.0'

server_scripts {
    'config.lua',
    'server/main.lua'
}

dependency 'qb-core'

Dosyayı kaydedin.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

5. config.lua Ayarları

config.lua dosyasını açın ve aşağıdaki kodu yapıştırın:

Kod:
Config = {}

Config.BotName = 'ExxDev FiveM Logs'

-- İsteğe bağlı bot profil resmi.
-- Boş bırakırsanız webhookun kendi profil resmi kullanılır.
Config.AvatarURL = ''

Config.FooterText = 'ExxDev FiveM Log Sistemi'

-- Discord'a log gönderme aralığı.
-- Çok düşük yapmayın, aksi durumda Discord 429 hatası verebilir.
Config.SendIntervalMilliseconds = 1200

-- Hafızada en fazla tutulacak log miktarı.
Config.MaxQueue = 250

-- Bu değerin altındaki para işlemleri Discord'a gönderilmez.
-- Bütün para hareketleri için 1 yapabilirsiniz.
Config.MinimumMoneyLog = 5000

Config.LogResourceStart = true

Config.Webhooks = {
    -- Sadece tek kanal kullanacaksanız webhooku General alanına yazın.
    -- Diğer alanları boş bırakırsanız bütün loglar General kanalına gider.
    General = 'WEBHOOK_ADRESINI_BURAYA_YAZ',

    Player = '',
    Money = '',
    Combat = '',
    Item = '',
    Vehicle = '',
    Admin = ''
}

Config.Colors = {
    Green = 5763719,
    Red = 15548997,
    Yellow = 16705372,
    Blue = 3447003,
    Purple = 10181046,
    Orange = 15105570,
    Grey = 9807270
}

Tek kanal kullanacaksanız

Sadece genel-log kanalında bir webhook oluşturun ve şu alana yapıştırın:

Kod:
General = 'https://discord.com/api/webhooks/WEBHOOK_ID/WEBHOOK_TOKEN',

Diğer alanları boş bırakın:

Kod:
Player = '',
Money = '',
Combat = '',
Item = '',
Vehicle = '',
Admin = ''

Her log için ayrı kanal kullanacaksanız

Her kanalda ayrı webhook oluşturup ilgili alana ekleyin:

Kod:
Config.Webhooks = {
    General = 'GENEL_LOG_WEBHOOK',

    Player = 'GIRIS_CIKIS_WEBHOOK',
    Money = 'PARA_LOG_WEBHOOK',
    Combat = 'OLUM_LOG_WEBHOOK',
    Item = 'ITEM_LOG_WEBHOOK',
    Vehicle = 'ARAC_LOG_WEBHOOK',
    Admin = 'ADMIN_LOG_WEBHOOK'
}

Webhook adreslerinin başında ve sonunda gereksiz boşluk bırakmayın.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

6. server/main.lua Dosyası

server klasöründeki main.lua dosyasını açın ve aşağıdaki kodun tamamını yapıştırın:

Kod:
local QBCore = exports['qb-core']:GetCoreObject()

local logQueue = {}
local queueRunning = false
local playerCache = {}

local function SafeText(value, maximumLength)
    local text = tostring(value or 'Bilinmiyor')
    maximumLength = tonumber(maximumLength) or 1000

    -- Discord etiket ve kod görünümü suistimalini azaltır.
    text = text:gsub('@', '@')
    text = text:gsub('`', 'ˋ')

    if #text > maximumLength then
        text = text:sub(1, maximumLength - 3) .. '...'
    end

    return text
end

local function GetIdentifiers(src)
    local identifiers = {
        license = 'Bulunamadı',
        discord = 'Bulunamadı',
        fivem = 'Bulunamadı',
        steam = 'Bulunamadı'
    }

    for _, identifier in ipairs(GetPlayerIdentifiers(src)) do
        if identifier:sub(1, 8) == 'license:' then
            identifiers.license = identifier
        elseif identifier:sub(1, 8) == 'discord:' then
            identifiers.discord = identifier
        elseif identifier:sub(1, 6) == 'fivem:' then
            identifiers.fivem = identifier
        elseif identifier:sub(1, 6) == 'steam:' then
            identifiers.steam = identifier
        end
    end

    return identifiers
end

local function FormatDiscordIdentifier(identifier)
    if not identifier or identifier == 'Bulunamadı' then
        return 'Bulunamadı'
    end

    local discordId = identifier:gsub('discord:', '')

    return ('<@%s> | `%s`'):format(discordId, discordId)
end

local function GetCharacterName(playerData)
    if not playerData or not playerData.charinfo then
        return 'Karakter yüklenmedi'
    end

    local firstName = playerData.charinfo.firstname or ''
    local lastName = playerData.charinfo.lastname or ''

    return SafeText(firstName .. ' ' .. lastName, 100)
end

local function GetJobName(playerData)
    if not playerData or not playerData.job then
        return 'Bilinmiyor'
    end

    local jobLabel = playerData.job.label or playerData.job.name or 'Bilinmiyor'
    local gradeName = 'Bilinmiyor'

    if playerData.job.grade then
        gradeName = playerData.job.grade.name
            or tostring(playerData.job.grade.level or 'Bilinmiyor')
    end

    return SafeText(('%s / %s'):format(jobLabel, gradeName), 100)
end

local function GetWebhook(category)
    local selectedWebhook = Config.Webhooks[category]

    if selectedWebhook and selectedWebhook ~= '' then
        return selectedWebhook
    end

    return Config.Webhooks.General
end

local function StartQueue()
    if queueRunning then
        return
    end

    queueRunning = true

    CreateThread(function()
        while #logQueue > 0 do
            local logData = table.remove(logQueue, 1)

            PerformHttpRequest(
                logData.webhook,
                function(statusCode, responseBody, responseHeaders, errorData)
                    if statusCode ~= 200 and statusCode ~= 204 then
                        print(
                            ('[ExxDev Discord Logs] Gönderim hatası! HTTP: %s | Hata: %s')
                            :format(
                                tostring(statusCode),
                                tostring(errorData or responseBody)
                            )
                        )

                        if statusCode == 401 or statusCode == 404 then
                            print(
                                '[ExxDev Discord Logs] Webhook adresi geçersiz veya silinmiş olabilir.'
                            )
                        elseif statusCode == 429 then
                            print(
                                '[ExxDev Discord Logs] Discord hız sınırına ulaşıldı. Gönderim aralığını yükseltin.'
                            )
                        end
                    end
                end,
                'POST',
                logData.payload,
                {
                    ['Content-Type'] = 'application/json'
                }
            )

            Wait(
                math.max(
                    tonumber(Config.SendIntervalMilliseconds) or 1200,
                    500
                )
            )
        end

        queueRunning = false

        -- Yeni log tam thread biterken geldiyse tekrar başlatır.
        if #logQueue > 0 then
            StartQueue()
        end
    end)
end

local function SendLog(category, title, description, color, fields)
    category = tostring(category or 'General')

    local webhook = GetWebhook(category)

    if not webhook or webhook == '' then
        print(
            ('[ExxDev Discord Logs] %s kategorisi için webhook ayarlanmamış.')
            :format(category)
        )

        return false
    end

    if webhook:find(
        'https://discord.com/api/webhooks/',
        1,
        true
    ) ~= 1 then
        print(
            ('[ExxDev Discord Logs] %s kategorisindeki webhook adresi geçersiz.')
            :format(category)
        )

        return false
    end

    local embedFields = {}

    if type(fields) == 'table' then
        for index = 1, math.min(#fields, 25) do
            local field = fields[index]

            embedFields[#embedFields + 1] = {
                name = SafeText(field.name, 250),
                value = SafeText(field.value, 1000),
                inline = field.inline == true
            }
        end
    end

    local embed = {
        title = SafeText(title, 250),
        description = SafeText(description, 4000),
        color = tonumber(color) or Config.Colors.Blue,
        fields = embedFields,
        timestamp = os.date('!%Y-%m-%dT%H:%M:%SZ'),
        footer = {
            text = SafeText(Config.FooterText, 200)
        }
    }

    local payloadData = {
        username = SafeText(Config.BotName, 80),

        -- Oyuncu isimleri veya log metinleri etiket içerirse
        -- Discord rollerine bildirim gönderilmesini engeller.
        allowed_mentions = {
            parse = {}
        },

        embeds = {
            embed
        }
    }

    if Config.AvatarURL and Config.AvatarURL ~= '' then
        payloadData.avatar_url = Config.AvatarURL
    end

    local maximumQueue = tonumber(Config.MaxQueue) or 250

    if #logQueue >= maximumQueue then
        table.remove(logQueue, 1)

        print(
            '[ExxDev Discord Logs] Log sırası dolduğu için en eski kayıt kaldırıldı.'
        )
    end

    logQueue[#logQueue + 1] = {
        webhook = webhook,
        payload = json.encode(payloadData)
    }

    StartQueue()
    return true
end

-- Başka server scriptleri bu exportu kullanarak log gönderebilir.
exports('SendLog', SendLog)

-- Sadece server tarafındaki scriptler TriggerEvent ile kullanabilir.
-- Bunu RegisterNetEvent olarak değiştirmeyin.
AddEventHandler(
    'exxdev_discordlogs:server:send',
    function(category, title, description, color, fields)
        SendLog(category, title, description, color, fields)
    end
)

-- QBCore karakter yükleme ve giriş logu
AddEventHandler('QBCore:Server:PlayerLoaded', function(Player)
    if not Player or not Player.PlayerData then
        return
    end

    local playerData = Player.PlayerData
    local src = tonumber(playerData.source)

    if not src then
        return
    end

    local identifiers = GetIdentifiers(src)
    local characterName = GetCharacterName(playerData)

    playerCache[src] = {
        gameName = GetPlayerName(src) or 'Bilinmiyor',
        characterName = characterName,
        citizenId = playerData.citizenid or 'Bilinmiyor',
        license = identifiers.license,
        discord = identifiers.discord,
        job = GetJobName(playerData)
    }

    SendLog(
        'Player',
        '🟢 Oyuncu Sunucuya Girdi',
        'Bir oyuncu karakterini başarıyla yükledi.',
        Config.Colors.Green,
        {
            {
                name = 'Oyuncu',
                value = GetPlayerName(src) or 'Bilinmiyor',
                inline = true
            },
            {
                name = 'Server ID',
                value = src,
                inline = true
            },
            {
                name = 'Karakter',
                value = characterName,
                inline = true
            },
            {
                name = 'Citizen ID',
                value = playerData.citizenid or 'Bilinmiyor',
                inline = true
            },
            {
                name = 'Meslek',
                value = GetJobName(playerData),
                inline = true
            },
            {
                name = 'Discord',
                value = FormatDiscordIdentifier(identifiers.discord),
                inline = false
            },
            {
                name = 'Rockstar License',
                value = identifiers.license,
                inline = false
            }
        }
    )
end)

-- Oyuncu çıkış logu
AddEventHandler(
    'playerDropped',
    function(reason, resourceName, clientDropReason)
        local src = source
        local cached = playerCache[src]
        local identifiers = GetIdentifiers(src)

        SendLog(
            'Player',
            '🔴 Oyuncu Sunucudan Ayrıldı',
            'Bir oyuncunun sunucu bağlantısı sona erdi.',
            Config.Colors.Red,
            {
                {
                    name = 'Oyuncu',
                    value = cached and cached.gameName
                        or GetPlayerName(src)
                        or 'Bilinmiyor',
                    inline = true
                },
                {
                    name = 'Server ID',
                    value = src,
                    inline = true
                },
                {
                    name = 'Karakter',
                    value = cached and cached.characterName
                        or 'Karakter bilgisi bulunamadı',
                    inline = true
                },
                {
                    name = 'Citizen ID',
                    value = cached and cached.citizenId
                        or 'Bilinmiyor',
                    inline = true
                },
                {
                    name = 'Ayrılma Nedeni',
                    value = reason or 'Bilinmiyor',
                    inline = false
                },
                {
                    name = 'Discord',
                    value = FormatDiscordIdentifier(
                        cached and cached.discord
                        or identifiers.discord
                    ),
                    inline = false
                },
                {
                    name = 'Rockstar License',
                    value = cached and cached.license
                        or identifiers.license,
                    inline = false
                }
            }
        )

        playerCache[src] = nil
    end
)

-- QBCore para hareketleri
AddEventHandler(
    'QBCore:Server:OnMoneyChange',
    function(playerSource, moneyType, amount, action, reason)
        local src = tonumber(playerSource)
        amount = tonumber(amount)

        if not src or not amount then
            return
        end

        if amount < (tonumber(Config.MinimumMoneyLog) or 1) then
            return
        end

        local Player = QBCore.Functions.GetPlayer(src)
        local cached = playerCache[src]

        local balance = 'Bilinmiyor'

        if Player
            and Player.PlayerData
            and Player.PlayerData.money
            and Player.PlayerData.money[moneyType]
        then
            balance = Player.PlayerData.money[moneyType]
        end

        local actionNames = {
            add = 'Para Eklendi',
            remove = 'Para Çıkarıldı',
            set = 'Para Bakiyesi Ayarlandı'
        }

        local actionColors = {
            add = Config.Colors.Green,
            remove = Config.Colors.Red,
            set = Config.Colors.Yellow
        }

        SendLog(
            'Money',
            '💰 ' .. (actionNames[action] or 'Para Hareketi'),
            'Bir oyuncunun para bakiyesinde değişiklik gerçekleşti.',
            actionColors[action] or Config.Colors.Yellow,
            {
                {
                    name = 'Oyuncu',
                    value = GetPlayerName(src) or 'Bilinmiyor',
                    inline = true
                },
                {
                    name = 'Karakter',
                    value = cached and cached.characterName
                        or 'Bilinmiyor',
                    inline = true
                },
                {
                    name = 'Citizen ID',
                    value = Player
                        and Player.PlayerData.citizenid
                        or cached
                        and cached.citizenId
                        or 'Bilinmiyor',
                    inline = true
                },
                {
                    name = 'Para Türü',
                    value = moneyType or 'Bilinmiyor',
                    inline = true
                },
                {
                    name = 'İşlem',
                    value = actionNames[action] or action or 'Bilinmiyor',
                    inline = true
                },
                {
                    name = 'Miktar',
                    value = ('$%s'):format(amount),
                    inline = true
                },
                {
                    name = 'Yeni Bakiye',
                    value = ('$%s'):format(balance),
                    inline = true
                },
                {
                    name = 'İşlem Nedeni',
                    value = reason or 'unknown',
                    inline = false
                }
            }
        )
    end
)

-- Bir oyuncu başka oyuncu tarafından öldürüldüğünde
AddEventHandler(
    'baseevents:onPlayerKilled',
    function(killerId, deathData)
        local victimId = source
        killerId = tonumber(killerId)

        local victimCache = playerCache[victimId]
        local killerCache = killerId and playerCache[killerId] or nil

        local weaponHash = 'Bilinmiyor'

        if type(deathData) == 'table' and deathData.weaponhash then
            weaponHash = tostring(deathData.weaponhash)
        end

        SendLog(
            'Combat',
            '💀 Oyuncu Öldürüldü',
            'Bir oyuncu başka bir oyuncu tarafından öldürüldü.',
            Config.Colors.Red,
            {
                {
                    name = 'Ölen Oyuncu',
                    value = GetPlayerName(victimId) or 'Bilinmiyor',
                    inline = true
                },
                {
                    name = 'Ölen Karakter',
                    value = victimCache and victimCache.characterName
                        or 'Bilinmiyor',
                    inline = true
                },
                {
                    name = 'Öldüren Oyuncu',
                    value = killerId
                        and GetPlayerName(killerId)
                        or 'Bilinmiyor',
                    inline = true
                },
                {
                    name = 'Öldüren Karakter',
                    value = killerCache and killerCache.characterName
                        or 'Bilinmiyor',
                    inline = true
                },
                {
                    name = 'Ölen ID',
                    value = victimId,
                    inline = true
                },
                {
                    name = 'Öldüren ID',
                    value = killerId or 'Bilinmiyor',
                    inline = true
                },
                {
                    name = 'Silah Hash',
                    value = weaponHash,
                    inline = false
                }
            }
        )
    end
)

-- Oyuncu çevresel sebeple veya kendi kendine öldüğünde
AddEventHandler(
    'baseevents:onPlayerDied',
    function(killedBy, position)
        local src = source
        local cached = playerCache[src]

        local positionText = 'Bilinmiyor'

        if type(position) == 'table' then
            positionText = ('X: %s | Y: %s | Z: %s'):format(
                tostring(position[1] or '?'),
                tostring(position[2] or '?'),
                tostring(position[3] or '?')
            )
        end

        SendLog(
            'Combat',
            '☠️ Oyuncu Hayatını Kaybetti',
            'Oyuncu çevresel bir sebeple veya kendi kendine öldü.',
            Config.Colors.Orange,
            {
                {
                    name = 'Oyuncu',
                    value = GetPlayerName(src) or 'Bilinmiyor',
                    inline = true
                },
                {
                    name = 'Karakter',
                    value = cached and cached.characterName
                        or 'Bilinmiyor',
                    inline = true
                },
                {
                    name = 'Server ID',
                    value = src,
                    inline = true
                },
                {
                    name = 'Konum',
                    value = positionText,
                    inline = false
                }
            }
        )
    end
)

-- txAdmin konsolundan çalıştırılabilen test komutu
RegisterCommand('testdiscordlog', function(source)
    if source ~= 0 then
        return
    end

    SendLog(
        'General',
        '✅ Discord Log Testi Başarılı',
        'FiveM sunucusu Discord webhookuna başarıyla erişti.',
        Config.Colors.Green,
        {
            {
                name = 'Resource',
                value = GetCurrentResourceName(),
                inline = true
            },
            {
                name = 'Durum',
                value = 'Aktif',
                inline = true
            }
        }
    )

    print('[ExxDev Discord Logs] Test logu sıraya eklendi.')
end, false)

AddEventHandler('onResourceStart', function(resourceName)
    if resourceName ~= GetCurrentResourceName() then
        return
    end

    if not Config.LogResourceStart then
        return
    end

    SetTimeout(2000, function()
        SendLog(
            'General',
            '⚙️ Log Sistemi Başlatıldı',
            'Discord log resource’u başarıyla çalıştırıldı.',
            Config.Colors.Blue,
            {
                {
                    name = 'Resource',
                    value = GetCurrentResourceName(),
                    inline = true
                },
                {
                    name = 'Sürüm',
                    value = '1.0.0',
                    inline = true
                }
            }
        )
    end)
end)

Dosyayı kaydedin.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

7. server.cfg Ayarı

server.cfg dosyanızı açın.

Aşağıdaki satırların doğru sırada bulunduğundan emin olun:

Kod:
ensure baseevents
ensure qb-core
ensure exxdev_discordlogs

Log sistemi qb-core’dan sonra başlamalıdır.

baseevents resource’u ölüm ve öldürme logları için gereklidir. Standart FXServer kurulumlarında genellikle resources/[system]/baseevents klasöründe bulunur.

Sunucuyu yeniden başlatabilir veya txAdmin konsolunda şu komutları kullanabilirsiniz:

Kod:
refresh
ensure exxdev_discordlogs

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

8. Sistemi Test Etme

txAdmin konsolunu açın ve şu komutu çalıştırın:

Kod:
testdiscordlog

Her şey doğruysa Discord kanalınıza şu başlıkla bir mesaj gelecektir:

Kod:
✅ Discord Log Testi Başarılı

Ardından sunucuya girip aşağıdaki işlemleri test edin:

  1. Karakterinizi yükleyin.
  2. Sunucudan çıkış yapın.
  3. Yüksek miktarda para ekleyip çıkarın.
  4. Başka bir oyuncuyla ölüm sistemini test edin.
  5. txAdmin konsolunda hata bulunup bulunmadığını kontrol edin.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

9. Item Logu Nasıl Eklenir?

Item logu, itemi veren veya kaldıran scriptin server.lua dosyasına eklenmelidir.

Örnek bir item verme işlemi:

Kod:
local added = exports['qb-inventory']:AddItem(
    src,
    'water_bottle',
    5,
    false,
    false,
    'meslek-odulu'
)

İşlem başarılı olduktan sonra şu kodu ekleyin:

Kod:
if added then
    exports['exxdev_discordlogs']:SendLog(
        'Item',
        '📦 Oyuncuya Item Verildi',
        'Bir oyuncunun envanterine item eklendi.',
        5763719,
        {
            {
                name = 'Oyuncu',
                value = GetPlayerName(src) or 'Bilinmiyor',
                inline = true
            },
            {
                name = 'Server ID',
                value = src,
                inline = true
            },
            {
                name = 'Item',
                value = 'water_bottle',
                inline = true
            },
            {
                name = 'Miktar',
                value = 5,
                inline = true
            },
            {
                name = 'İşlem Nedeni',
                value = 'Meslek ödülü',
                inline = false
            }
        }
    )
end

Önemli: Logu item ekleme işleminden önce göndermeyin. İşlem başarısız olsa bile Discord’a başarılı gibi yansıyabilir.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

10. Araç Satın Alma Logu

Araç veritabanına başarıyla kaydedildikten sonra araç satış scriptinin server dosyasına şu yapıyı ekleyebilirsiniz:

Kod:
exports['exxdev_discordlogs']:SendLog(
    'Vehicle',
    '🚗 Araç Satın Alındı',
    'Bir oyuncu galeriden araç satın aldı.',
    3447003,
    {
        {
            name = 'Oyuncu',
            value = GetPlayerName(src) or 'Bilinmiyor',
            inline = true
        },
        {
            name = 'Server ID',
            value = src,
            inline = true
        },
        {
            name = 'Araç',
            value = vehicleModel,
            inline = true
        },
        {
            name = 'Plaka',
            value = plate,
            inline = true
        },
        {
            name = 'Fiyat',
            value = ('$%s'):format(vehiclePrice),
            inline = true
        }
    }
)

vehicleModel, plate ve vehiclePrice değişkenlerini kullandığınız galeri scriptine göre düzenlemeniz gerekir.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

11. Yönetici İşlem Logu

Bir yönetici komutu başarıyla uygulandıktan sonra aşağıdaki yapıyı kullanabilirsiniz:

Kod:
exports['exxdev_discordlogs']:SendLog(
    'Admin',
    '🛡️ Yönetici İşlemi',
    'Bir yönetici oyuncu üzerinde işlem uyguladı.',
    10181046,
    {
        {
            name = 'Yetkili',
            value = GetPlayerName(source) or 'Konsol',
            inline = true
        },
        {
            name = 'Yetkili ID',
            value = source,
            inline = true
        },
        {
            name = 'Hedef',
            value = GetPlayerName(targetId) or 'Bilinmiyor',
            inline = true
        },
        {
            name = 'Hedef ID',
            value = targetId,
            inline = true
        },
        {
            name = 'İşlem',
            value = 'Para verildi',
            inline = false
        },
        {
            name = 'Miktar',
            value = amount,
            inline = true
        }
    }
)

Log kodunu yetki kontrolünden ve başarılı işlemden sonra çalıştırın.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

12. TriggerEvent ile Log Gönderme

Export kullanmak istemiyorsanız başka bir server dosyasından şu şekilde log gönderebilirsiniz:

Kod:
TriggerEvent(
    'exxdev_discordlogs:server:send',
    'General',
    'Özel İşlem Logu',
    'Sunucuda özel bir işlem gerçekleşti.',
    3447003,
    {
        {
            name = 'Açıklama',
            value = 'Buraya işlem bilgisi yazılabilir.',
            inline = false
        }
    }
)

Güvenlik uyarısı:

Aşağıdaki kullanımı yapmayın:

Kod:
RegisterNetEvent('exxdev_discordlogs:server:send')

Log eventi client tarafından erişilebilir hâle getirilirse hile kullanan kişiler Discord kanallarınıza sahte log veya log spam gönderebilir.

Log gönderimi yalnızca server dosyalarında yapılmalıdır.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

13. Sık Karşılaşılan Hatalar

Discord’a hiçbir log gelmiyor

  • Webhook adresini yeniden kopyalayın.
  • Webhook adresinin başında veya sonunda boşluk olmadığını kontrol edin.
  • config.lua dosyasını kaydettiğinizden emin olun.
  • Resource’u yeniden başlatın.
  • txAdmin konsolunda testdiscordlog komutunu kullanın.
  • Sunucunun Discord’a HTTPS bağlantısı kurabildiğini kontrol edin.

HTTP 401 veya 404 hatası

Webhook silinmiş, değiştirilmiş veya yanlış kopyalanmış olabilir. Discord’dan yeni bir webhook oluşturun.

HTTP 429 hatası

Kısa sürede çok fazla log gönderilmiştir.

config.lua içerisindeki değeri yükseltin:

Kod:
Config.SendIntervalMilliseconds = 2000

Ayrıca para, item ve giriş logları için ayrı webhooklar kullanabilirsiniz.

Para logları gelmiyor

İşlem miktarı belirlediğiniz sınırın altında olabilir:

Kod:
Config.MinimumMoneyLog = 5000

Bütün para işlemlerini görmek için:

Kod:
Config.MinimumMoneyLog = 1

Ancak bu ayar marketteki küçük alışverişleri bile loglayacağı için Discord kanalında çok fazla mesaj oluşturabilir.

Ölüm logları gelmiyor

server.cfg içerisinde baseevents’in başlatıldığını kontrol edin:

Kod:
ensure baseevents

Resource klasörünün şu alanda bulunması gerekir:

Kod:
resources/[system]/baseevents

No such export SendLog hatası

exxdev_discordlogs, log göndermeye çalışan scriptten önce başlamalıdır:

Kod:
ensure exxdev_discordlogs
ensure meslek-scripti
ensure galeri-scripti

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

14. Güvenlik Kontrol Listesi

  • Webhook adreslerini client.lua dosyasına yazmayın.
  • Webhook adreslerini GitHub’da herkese açık paylaşmayın.
  • Webhook adreslerini ekran görüntülerinde göstermeyin.
  • Log kanallarını normal oyunculara açmayın.
  • Bot tokenini webhook adresi yerine kullanmayın.
  • Oyuncuların log eventini doğrudan tetiklemesine izin vermeyin.
  • Log mesajlarında şifre veya ödeme bilgisi bulundurmayın.
  • Webhook sızarsa eskisini silip yenisini oluşturun.
  • Her işlem için ayrı log kanalı kullanmayı değerlendirin.
  • Discord 429 hatalarını düzenli olarak kontrol edin.
  • Resource dosyalarının yedeğini alın.

Not: baseevents ölüm bilgisi istemci tarafından bildirildiği için ölüm loglarını tek başına kesin hile veya ban kanıtı olarak kullanmayın. Bu loglar yönetim incelemesi ve olay takibi içindir.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Sonuç

Bu işlemlerin ardından QBCore FiveM sunucunuzda çalışan temel bir Discord log sistemine sahip olacaksınız.

Sistem otomatik olarak:

  • Oyuncu giriş ve çıkışlarını
  • Karakter ve Citizen ID bilgilerini
  • Belirlenen miktarın üzerindeki para hareketlerini
  • Oyuncu ölüm ve öldürme işlemlerini
  • Resource başlangıç durumunu

Discord’a gönderecektir.

Item, araç, meslek ve yönetici logları ilgili scriptlerin server dosyalarına verilen örnekler kullanılarak eklenebilir.


Kodlama ve anlatım: ExxDev

Webhook adreslerinizi hiçbir zaman herkese açık olarak paylaşmayın.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Resmî Kaynaklar

 
Geri
Üst