Server Exports
Server-side exports for GKSPHONE V2 — messages, numbers, mail, map locations, and player phone data.
Messages
SendMessage
--- Sends a message from any number to another number
--- @param fromNumber string Sender phone number
--- @param toNumber string Receiver phone number
--- @param message string|table|vector2 Message content, table with coords {x,y}, or vector2 for GPS
--- @param options table|nil { skipBlockCheck = boolean, skipSIMUsage = boolean, saveSenderCopy = boolean }
--- skipBlockCheck -- deliver even if the receiver blocked the sender
--- skipSIMUsage -- do not count the message against the sender's SIM package
--- saveSenderCopy -- keep a copy in the sender's thread (default true)
--- @return table { status = boolean, messageId = number|nil, error = string|nil }
exports["gksphone"]:SendMessage("101-11111", "101-22222", "Hello!")
exports["gksphone"]:SendMessage("101-11111", "101-22222", "Hello!", {
skipBlockCheck = true,
saveSenderCopy = false
})SendSystemMessage
--- Sends a system/virtual message to a player
--- @param targetPhoneNumber string Receiver phone number
--- @param message string|table|vector2 Message content, table with coords {x,y}, or vector2 for GPS
--- @param senderNumber string Sender number/name (e.g. "Delivery", "LSCustom", "Police")
--- @return table { status = boolean, messageId = number|nil, error = string|nil }
local result = exports["gksphone"]:SendSystemMessage("101-22222", "Test Message", "Delivery")
-- result = { status = true, messageId = 12345 }
-- Error: { status = false, error = "Missing parameters" }BroadcastSystemMessage
--- Broadcasts a system message to all online players
--- @param message string|table Message content
--- @param senderNumber string Sender number/name
--- @return table { status = boolean, sentCount = number }
exports["gksphone"]:BroadcastSystemMessage("Hello!", "Police")Calls
CreateCall
local data = {
number = "5551234", -- Phone number (required if no job/company)
job = "police",
hideNumber = false -- Anonymous / hidden caller ID
}
exports['gksphone']:CreateCall(source, data)
-- returns false, "invalid_source" | "invalid_data" (job checks run on client)IsInCall
local inCall, callId, call = exports['gksphone']:IsInCall(source)
-- returns true, 5000, callDataGetCall
local call = exports['gksphone']:GetCall(callId)
-- returns
{
callerSource = 1,
callerPhone = '1111',
callerPhoneUniq = 'GKS11111',
calltype = 'calling', -- calling or vidmeet
time = 0, -- The os.time() when the call started
status = true,
is_anonymous = true,
isJob = false,
receivers = {} -- {receiverSource = 2, receiverPhone, receiverPhoneUniq, is_accepts}
}EndCall
exports['gksphone']:EndCall(source)Send Mail
local src = source or -1
local MailData = {
sender = 'GKSHOP',
image = '/html/img/icons/mail.png',
subject = "GKSPHONE",
message = 'TEST',
buttons = { --- If you don't want it to be a button, please remove it.
{
label = "Accept",
type = "client_event",
event = "myresource:client:accept",
data = { id = 123 },
color = "green",
clearOnClick = true
},
{
label = "Reject",
type = "server_event",
event = "myresource:server:reject",
data = { id = 123 },
color = "red",
clearOnClick = false
}
},
attachments = {
"https://example.com/photo1.png",
"https://example.com/photo2.png"
}
}
exports["gksphone"]:SendNewMail(src, MailData)Send Offline Mail
-- citizenID => QB Citizen Id
local xPlayer = QBCore.Functions.GetPlayer(source)
local citizenID = xPlayer.PlayerData.citizenid
local MailData = {
sender = 'GKSHOP',
image = '/html/img/icons/mail.png',
subject = "GKSPHONE",
message = 'TEST',
buttons = { --- If you don't want it to be a button, please remove it.
{
label = "Accept",
type = "client_event",
event = "myresource:client:accept",
data = { id = 123 },
color = "green"
},
{
label = "Reject",
type = "server_event",
event = "myresource:server:reject",
data = { id = 123 },
color = "red",
clearOnClick = false
}
},
attachments = {
"https://example.com/photo1.png",
"https://example.com/photo2.png"
}
}
exports["gksphone"]:SendNewMailOffline(citizenID, MailData)SendPlayerMail
Sends mail from one player to another, as that player — unlike SendNewMail, which delivers a system mail.
--- @param source number Sender. Must have a phone with a mail address
--- @param payload table
--- to_mail string required. Aliases: to, recipient_mail, email
--- subject string required. Alias: title
--- message string required unless attachments are given. Aliases: body, content
--- attachments table optional. Aliases: attachment, photos, images
--- image string optional icon. Alias: icon
--- reply_to string optional. Alias: replyTo
--- buttons table optional. Aliases: actions, button
--- @return table { status = boolean, inboxId = number, sentId = number, sentMail = table }
--- error = "no_phone" | "invalid_to" | "invalid_subject" | "empty_body"
local result = exports["gksphone"]:SendPlayerMail(source, {
to_mail = "[email protected]",
subject = "Invoice",
message = "Attached."
})The recipient address is lowercased and stripped of whitespace before lookup.
Billing
New Billing
-- src => Player's ID
-- label => Billing description
-- society => By which job the billingwas created
-- senderBilling => Who is the person sending the billing?
-- senderID => ESX Identifier of the billing originator
-- amount => Billing price
local src = source
local label = "Excessive Speed"
local society = "police"
local senderBilling = "GKSHOP XENKNIGHT" -- Player Name
local senderID = "char1:4b110a7811" -- xPlayer.identifier
local amount = 500
exports["gksphone"]:NewBilling(src, label, society, senderBilling, senderID, amount)Is Unpaid Bill
To inquire if the player has any outstanding bills
local xPlayer = QBCore.Functions.GetPlayer(source)
local citizenID = xPlayer.PlayerData.citizenid
local isBills = exports["gksphone"]:IsUnpaidBillsbyCid(citizenID)
print(isBills) -- true or falseMisc
Send Notification
-- src => Player's ID
local src = source
local NotifData = {
title = "Notification header", -- Notification header
message = "Notification Message", -- Notification content message
icon = '/html/img/icons/messages.png', -- Icon of the notification
duration = 5000, -- specify how many seconds,
type = "success", -- the home screen will also appear on the notification side.
buttonactive = false, -- Activate if you want to use the button function
button = {
buttonEvent = "gksphone:client:Test", -- event name to use if the button approves
buttonData = "test", -- If you want to transfer any data in the button
}
}
exports["gksphone"]:sendNotification(src, NotifData)New Number
local src = source -- player id
local phoneID = uniqID or nil
local NewNumber = "555555" -- example
local newNumber = exports["gksphone"]:NewNumber(src, phoneID, NewNumber)
print(newNumber) -- true/falseChange Number
Change phone number
local phoneID = "GKSXXXXXXX" -- Phone Uniq ID
local oldNumber = "5555"
local newNumber = "2222"
local updateContacts = true -- Whether to update contacts with the new number
local changeNumber = exports["gksphone"]:ChangeNumber(phoneID, oldNumber, newNumber, updateContacts)
print(changeNumber) -- true or falseEmergency Alert
local title = "Emergency Alert"
local message = "Test Alert"
exports["gksphone"]:EmergencyAlert(title, message)GetPhoneResetTarget
--- The phone a reset would act on, so you can confirm before committing
--- @param source number
--- @return string|nil phoneUniqueId
local phoneId = exports["gksphone"]:GetPhoneResetTarget(source)ResetPhoneData
--- Resets a phone to its as-new state and issues a new number
--- @param target number|string Player source or phone unique_id (works while offline)
--- @param options table|nil { keepNumber = boolean } Wipe content but keep the current number
--- @return boolean ok, string|nil reason, table|nil result
--- reason = "bad_target" | "no_phone" | "unknown_phone"
local ok, reason, result = exports["gksphone"]:ResetPhoneData(source)
-- result = { phoneId = "PHONE-1234", identifier = "char1:xxx", oldNumber = "101-22222", newNumber = "101-55555" }
exports["gksphone"]:ResetPhoneData("PHONE-1234") -- offline player
exports["gksphone"]:ResetPhoneData(source, { keepNumber = true }) -- wipe content, keep the numberWipePhoneData
--- Deletes phone content only (messages, contacts, gallery, voice memos, notes)
--- Keeps the number and all settings — used when a phone-cracking attempt fails
--- @param phoneUniqID string
exports["gksphone"]:WipePhoneData("PHONE-1234")Services
Send Report
local src = source
local ped = GetPlayerPed(src)
local reportMessage = "Dispatch Message"
local reportPhoto = "Image Link" or nil
local job = "police" -- job code
local anonymous = false -- or true
local playerCoords = GetEntityCoords(ped)
local streedZone = "Street name" or "Unknown"
local sendDispatch = exports["gksphone"]:SendReport(src, reportMessage, reportPhoto, job, anonymous, playerCoords, streedZone)
print(sendDispatch) -- true/falseJob Status Change
The job in the Dispatch section is for opening and closing
local job = "police" -- job code
local status = true -- true or false
exports["gksphone"]:JobStatusChange(job, status)Job Status
local job = "police" -- job code
local jobStatus = exports["gksphone"]:IsJobStatus(job)
print(jobStatus) -- true or falseBank App
BankSaveHistory
--- Writes a transaction to a player's bank history, by source
--- @param source number
--- @param type number 1 = outgoing (-), 2 = incoming (+)
--- @param amount number
--- @param description string
--- @return boolean
local ok = exports["gksphone"]:BankSaveHistory(source, 1, 500, "Bank History Desc")bankHistorySave
--- The same write, addressed by phone number instead of source.
--- Use it when you have the number rather than a source.
--- @param phoneNumber string
--- @param type number 1 = outgoing (-), 2 = incoming (+)
--- @param amount number
--- @param description string
--- @param phoneUniqID string
--- @return boolean true only when the phone is loaded in memory.
--- The row is inserted either way, so a false here does not mean the
--- transaction was lost. The live in-app update is pushed only if the
--- player is online.
local ok = exports["gksphone"]:bankHistorySave("101-22222", 2, 500, "Salary", "GKS111111")Custom App
Add Custom App
local appData = {
name = "mdt", --- A unique name
icons = "/html/img/icons/mdt.png", -- logo url
description = "", -- App description that will appear in the app store
appurl = "https://cfx-nui-gksphone-app/ui/index.html", -- custom app url, required
blockedjobs = {},
allowjob = {},
show = true,
labelLangs = { -- App name by languages
af = "MDT",
ar = "MDT",
cs = "MDT",
de = "MDT",
en = "MDT",
es = "MDT",
fr = "MDT",
id = "MDT",
nl = "MDT",
["pt-PT"] = "MDT",
ro = "MDT",
sv = "MDT",
th = "MDT",
tr = "MDT",
uk = "MDT",
["zh-TW"] = "MDT"
}
}
exports["gksphone"]:AddCustomApp(appData)Stock market
Asset ids come from Config.Stocks in config.lua — lifeinvader, mazebank, postop, vangelico and flyus ship by default.
stockMarketAdd
--- Credits an asset to a phone's holdings
--- @param source number
--- @param coinid string Asset id from Config.Stocks
--- @param amount number
--- @param phoneUniqID string
--- @return boolean
local ok = exports["gksphone"]:stockMarketAdd(source, "lifeinvader", 5, "GKS111111")stockMarketRemove
--- Debits an asset from a phone's holdings
--- @param source number
--- @param coinid string Asset id from Config.Stocks
--- @param amount number
--- @param phoneUniqID string
--- @return boolean
local ok = exports["gksphone"]:stockMarketRemove(source, "lifeinvader", 5, "GKS111111")stockMarketTransfer
--- Moves an asset from one phone's holdings to another's, writing a history entry on both
--- @param source number
--- @param coinid string Asset id from Config.Stocks
--- @param amount number
--- @param phoneUniqID string Sender's phone unique id
--- @param TransferPhoneData table Receiver's phone data, e.g. from GetPhoneDataByNumber
--- @return boolean false when the asset is unknown or the sender holds less than amount
local target = exports["gksphone"]:GetPhoneDataByNumber("101-22222")
local ok = exports["gksphone"]:stockMarketTransfer(source, "lifeinvader", 5, "GKS111111", target)getstockmarket
--- Every asset with its current price
--- @return table
local market = exports["gksphone"]:getstockmarket()getstockmarketprice
--- @param symbol string Asset id, or its symbol in lowercase (e.g. "liv")
--- @return number|nil nil when the asset is unknown
local price = exports["gksphone"]:getstockmarketprice("lifeinvader")setstockprice
--- Sets a price and pushes it to every client. Rounded to 2 decimals.
--- @param symbol string Asset id, or its symbol in lowercase
--- @param newPrice number
exports["gksphone"]:setstockprice("lifeinvader", 175.5)updatemarket
--- Runs one price tick immediately, instead of waiting for Config.StockUpdateInterval
exports["gksphone"]:updatemarket()setstockprice and updatemarket are only registered when Config.StockMarket = true. The two getters are always available.
Live Stream
Add Cheer
local src = source
local amount = 5000
local addCheer = exports["gksphone"]:AddLiveStreamCheer(src, amount)
print(addCheer) -- true or falseAdd Coin
local src = source
local amount = 5000
local addCoin = exports["gksphone"]:AddLiveStreamCoin(src, amount)
print(addCoin) -- true or falseSocial Media
Toggle Verified
local app = 'squawk' -- 'squawk' or 'snapgram'
local username = '...' -- username in the app
local verified = 1 -- 0 = none / 1 = blue / 2 = yellow(only squawk)
local res = exports["gksphone"]:ToggleVerified(app, username, verified)
print(res) -- true or falseHeavy Jammer
heavyJammerByPhone
local phoneUniqueId = "GKS2026AAAAA" -- The unique identifier of the phone
local status = true -- true to enable jammer, false to disable
local message = "Test" -- Custom message displayed on the jammed phone
exports['gksphone']:heavyJammerByPhone(phoneUniqueId, status, message)
--- @return boolean Returns true if successful, false if phoneUniqueId is nilheavyJammerByPhones
Jam or unjam multiple phones at once.
-- Jam multiple phones
local phoneIds = {"PHONE_001", "PHONE_002", "PHONE_003"}
local count = exports['gksphone']:heavyJammerByPhones(phoneIds, true, "Signal blocked by authorities")
print(count .. " phones jammed")
-- Unjam all phones in the list
exports['gksphone']:heavyJammerByPhones(phoneIds, false, "")isPhoneJammed
Check if a specific phone is currently jammed.
-- phoneUniqueId (The unique identifier of the phone)
local isJammed = exports['gksphone']:isPhoneJammed("ABC123XYZ")
-- return boolean|nil
if isJammed == nil then
print("Invalid phone ID")
elseif isJammed then
print("Phone is currently jammed")
else
print("Phone has normal signal")
endgetJammedPhones
Get a list of all currently jammed phones.
local jammedPhones = exports['gksphone']:getJammedPhones()
--[[ Return table | { phoneUniqueId = { status, message, jammedAt }, ... }
{
["PHONE_ID_1"] = {
status = true, -- Always true for jammed phones
message = "...", -- The jammer message
jammedAt = 1704067200 -- Unix timestamp when jammed
},
["PHONE_ID_2"] = { ... }
}
]] --
for phoneId, data in pairs(jammedPhones) do
local duration = os.time() - data.jammedAt
print(string.format(
"Phone: %s | Message: %s | Jammed for: %d seconds",
phoneId,
data.message,
duration
))
endclearAllJammers
local cleared = exports['gksphone']:clearAllJammers()
-- return number | Count of jammers that were cleared
print(cleared .. " phones have been unjammed")Map / GPS
AddMapLocation
-- Add location to specific player
exports['gksphone']:AddMapLocation(playerSource, {
id = 'business_247_1',
position = { x = 25.7, y = -1346.7 },
name = '24/7 Store',
description = 'Open Now',
icon = 'https://...',
category = 'business'
})
-- Add location to all players (source = -1)
exports['gksphone']:AddMapLocation(-1, { id = 'event_party', position = { x = 100, y = 200 }, name = 'Party' })RemoveMapLocation
exports['gksphone']:RemoveMapLocation(playerSource, 'business_247_1')
exports['gksphone']:RemoveMapLocation(-1, 'event_party')UpdateMapLocation
exports['gksphone']:UpdateMapLocation(playerSource, 'vehicle_123', { position = { x = 150, y = -300 } })Live Activity
Glanceable status cards shown on the Dynamic Island and lock screen.
StartLiveActivity
local activity = {
id = "delivery:1234", -- required, unique per activity
app = "courier", -- source app key
title = "Delivery", -- card headline
subtitle = "Heading to drop", -- optional line under the title
icon = "/html/img/icons/courier.png", -- optional image url
color = "#34C759", -- optional accent color (hex)
state = "active", -- pending | active | paused | success | failed | canceled
progress = 0, -- optional 0-100
duration = 300, -- optional countdown in seconds (preferred on client)
timeout = 330000, -- optional lifetime in ms before auto-expiry
peek = true, -- optional, keeps the island visible after the phone closes
action = { -- optional button
label = "Open",
event = "myresource:openDelivery", -- client event triggered on press
data = { id = 1234 }, -- payload for that event
route = "/courier/" -- optional, opens the phone at this route
}
}
exports["gksphone"]:StartLiveActivity(source, activity) -- returns true, or false if the phone is off/unset (cached and shown later)UpdateLiveActivity
--- Only the supplied fields change. An unknown id is started instead of dropped.
exports["gksphone"]:UpdateLiveActivity(source, "delivery:1234", { progress = 60 })EndLiveActivity
--- @param state string|nil success | failed | canceled -- the card lingers briefly to show the outcome
--- @param opts table|nil { subtitle = string, immediate = boolean }
exports["gksphone"]:EndLiveActivity(source, "delivery:1234", "success", { subtitle = "Delivered" })
exports["gksphone"]:EndLiveActivity(source, "delivery:1234", "canceled", { immediate = true })Screen Damage
ApplyScreenDamage
--- Applies damage using the config entry for that trigger; the chance roll and
--- the amount are decided server-side
--- @param trigger string death | crash | fall | water
--- @return boolean applied -- false if disabled, no phone, or the roll failed
exports["gksphone"]:ApplyScreenDamage(source, "crash")DamageScreen
--- Removes an exact amount, no chance roll. For explosions, melee, etc.
--- @param amount number positive
--- @param reason string|nil shows up in the event payload
--- @return number|nil newHealth
local health = exports["gksphone"]:DamageScreen(source, 25, "explosion")SetScreenHealth
--- Sets health to an exact value. For partial repairs or admin tooling
--- @param value number 0-100
--- @return number|nil appliedHealth
exports["gksphone"]:SetScreenHealth(source, 100, "admin")SetWaterDamage
--- Toggles the water damage flag on its own, leaving health alone
--- @param state boolean
--- @return boolean
exports["gksphone"]:SetWaterDamage(source, false) -- e.g. a rice-bowl drying mechanicGetScreenHealth
local health = exports["gksphone"]:GetScreenHealth(source) -- number | nilGetScreenCondition
local condition = exports["gksphone"]:GetScreenCondition(source)
-- { phoneUniqueId, health, severity, waterDamage, lastDamageAt, lastRepairAt }GetScreenRepairQuote
--- Price without charging, so a shop UI can show it
--- @param targetSource number|nil the phone owner when a mechanic quotes someone else
local quote = exports["gksphone"]:GetScreenRepairQuote(source, targetSource)
-- { health, severity, waterDamage, price, selfRepair }RepairPhoneScreen
--- Repairs to 100, clears water damage and charges the bank
--- @param targetSource number|nil nil = self repair
--- @return boolean ok, string|nil reason, number|nil price
--- reason = "disabled" | "self_repair_disabled" | "no_permission" | "cooldown" | "no_phone" | "not_damaged" | "no_money"
local ok, reason, price = exports["gksphone"]:RepairPhoneScreen(source, targetSource)Phone Health
Battery health, charge-cycle wear and paid battery replacements. The client reports how much it charged; the server decides the wear.
GetPhoneHealth
local health = exports["gksphone"]:GetPhoneHealth(source)
-- { phoneUniqueId, batteryHealth, batteryCondition, chargeCycles, screenHealth, waterDamage, lastRepairAt }GetBatteryHealth
local battery = exports["gksphone"]:GetBatteryHealth(source) -- number | nilSetBatteryHealth
--- Sets battery health directly. For admin tooling or custom mechanics
--- @param value number 0-100
--- @return number|nil appliedHealth
exports["gksphone"]:SetBatteryHealth(source, 100)AddBatteryChargeProgress
--- Records charge progress and applies cycle wear once a full cycle completes
--- @param points number percentage points charged since the last report
--- @return number|nil batteryHealth
exports["gksphone"]:AddBatteryChargeProgress(source, 20)GetBatteryReplacementQuote
--- Price without charging, so a shop UI can show it
--- @param targetSource number|nil the phone owner when a mechanic quotes someone else
local quote = exports["gksphone"]:GetBatteryReplacementQuote(source, targetSource)
-- { health, condition, cycles, price, selfReplace }ReplacePhoneBattery
--- Resets battery health to 100, clears the cycle counter and charges the bank
--- @param targetSource number|nil nil = self replace
--- @return boolean ok, string|nil reason, number|nil price
--- reason = "disabled" | "self_replace_disabled" | "no_permission" | "cooldown" | "no_phone" | "not_worn" | "no_money"
local ok, reason, price = exports["gksphone"]:ReplacePhoneBattery(source, targetSource)Phone Data Exports
GetPhoneBySource
Find phone number with Source
local src = source
local phoneNumber = exports["gksphone"]:GetPhoneBySource(src)
print(phoneNumber)
-- OR
local xPlayer = QBCore.Functions.GetPlayer(source)
local phoneNumber = xPlayer.PlayerData.charinfo.phone
print(phoneNumber)GetSourceByPhone
Finding a source by phone number
local phoneNumber = number
local source = exports["gksphone"]:GetSourceByPhone(phoneNumber)
print(source)
-- OR
local phoneNumber = number
local xPlayer = QBCore.Functions.GetPlayerByPhone(phoneNumber)
local source = xPlayer.PlayerData.source
print(source)GetPhoneDataBySource
Access the data of the phone used with the Source number
local src = source
local phoneData = exports["gksphone"]:GetPhoneDataBySource(src)
print(json.encode(phoneData))GetPhoneDataByNumber
Accessing the phone's data with the phone number
local phoneNumber = number
local phoneData = exports["gksphone"]:GetPhoneDataByNumber(phoneNumber)
print(json.encode(phoneData))GetPhoneDataBySetupOwner
Accessing all phone data of the player
local xPlayer = QBCore.Functions.GetPlayer(source)
local citizenID = xPlayer.PlayerData.citizenid
local phoneData = exports["gksphone"]:GetPhoneDataBySetupOwner(citizenID)
print(json.encode(phoneData))GetPhoneDataByPhoneUniqID
Access phone data with the phone's UniqID
local phoneUniqID = id
local phoneData = exports["gksphone"]:GetPhoneDataByPhoneUniqID(phoneUniqID)
print(json.encode(phoneData))GetPhoneDataByCitizenID
Access phone data with the user's ID (You will access the data of the last phone the user opened)
local xPlayer = QBCore.Functions.GetPlayer(source)
local citizenID = xPlayer.PlayerData.citizenid
local phoneData = exports["gksphone"]:GetPhoneDataByCitizenID(citizenID)
print(json.encode(phoneData))GetPhoneLangBySource
The language the user chooses on the phone
local src = source
local PhoneLang = exports["gksphone"]:GetPhoneLangBySource(src)
print(PhoneLang)
