Koja Scripts

Developer API

Koja Chat server and client exports, import.lua, messages, channels, commands, hooks, badges, polls, moderation, events and default chat compatibility.

Developer API

Using it

Call the exports directly, or load import.lua to call the same functions without the colon.

your fxmanifest.lua
shared_script '@koja-chat/import.lua'
server.lua
exports['koja-chat']:SendSystem(source, 'Welcome!')

-- or with the import
KojaChat.SendSystem(source, 'Welcome!')

The import forwards every call to exports['koja-chat'] and works on the server and the client. Everything a resource registers (channels, commands, hooks) is removed when that resource stops.

Server API

Messages

FunctionDescription
SendMessage(target, message)Custom message. target is a player id, a list of ids or -1. Returns the message id, or false when message.text is missing
SendSystem(target, text, opts?)System line. opts = { icon, color, tag, ping }
SendError(target, text)Red error line
SendAs(source, channel, text, opts?)A player message on a channel: range, department split, overhead text and logs. Returns true, messageId or false, reason
Broadcast(channel, text, opts?)Message without an author to everyone who can see the channel. Returns the message id, or false for an unknown channel
Announce(text, opts?)Banner at the top of the screen and a chat line. opts = { title, author, color, duration }
Clear(target?)Clear the chat. No target = everyone

message = { text, channel?, style?, color?, icon?, tag?, author? = { name, id?, color?, badges? }, authorId?, global?, ping? }. The channel defaults to system. With authorId the author is built from that player (name and badges). A message on a channel that is not registered is shown on every tab.

SendAs skips the permission, mute, spam and filter checks unless you pass opts.validate = true. opts can contain style, tag, color, icon, overhead, range, anonymous, cost, account, result, name, nameColor and badges. Hooks, logs and the koja-chat:message event still run.

Styles: default, action, describe, try, twitter, anon, ad, whisper, report, system, error, announce.

server.lua
KojaChat.SendMessage(-1, {
    text = 'Bank robbery in progress at Fleeca!',
    tag = 'Dispatch',
    icon = 'siren',
    color = '#3b82f6',
    ping = true,
})

Message ids

SendMessage, Broadcast and SendAs give you the message id. Use it with DeleteMessage(id) to remove the message for everyone.

server.lua
local id = KojaChat.SendMessage(-1, { text = 'Server restart in 5 minutes', icon = 'clock' })
SetTimeout(60000, function() KojaChat.DeleteMessage(id) end)

Channels

server.lua
-- a new tab for mechanics only, 30 m range
KojaChat.RegisterChannel('radio', {
    label = 'Radio',
    color = '#22c55e',
    icon = 'radio',
    jobs = { 'mechanic' },
    range = 30.0,
})

-- fully custom visibility
KojaChat.RegisterChannel('vip', {
    label = 'VIP',
    color = '#facc15',
    icon = 'star',
    canSee = function(src, sender) return IsPlayerAceAllowed(src, 'vip') end,
    canSend = function(src) return IsPlayerAceAllowed(src, 'vip') end,
})
FunctionDescription
RegisterChannel(id, def)Adds a channel. Options: label, color, icon, range, admin, jobs, department, readonly, hidden, nameMode, canSee(src, sender), canSend(src). The same options as in Configuration
RemoveChannel(id)Removes a channel
GetChannels(src?)Without src: every channel as { id, label, color, icon, range }. With src: the tabs that player sees, with canSend
Refresh(src?)Sends tabs and suggestions again. Call it after something your canSee depends on changed. No src = everyone

Commands

server.lua
-- posts to a channel, no code needed
KojaChat.RegisterCommand('news', {
    channel = 'social',
    tag = 'Weazel News',
    color = '#f97316',
    icon = 'newspaper',
    jobs = { 'reporter' },
    params = { 'message' },
    help = 'Publish breaking news',
})

-- custom handler
KojaChat.RegisterCommand('roll', {
    help = 'Roll a dice',
    cooldown = 5,
    handler = function(src, args, text)
        KojaChat.SendAs(src, 'local', ('rolls the dice: %d'):format(math.random(6)), { style = 'action', overhead = true })
    end,
})

Options: help, params ('name' or { name, help }), then either handler(src, args, text) or channel with style, tag, color, icon, overhead, range, anonymous, cost and account. Also admin, jobs, cooldown, canUse(src), hidden (not listed in the suggestions) and console (also allow the server console).

Staff can use commands with jobs regardless of their job. RemoveCommand(name) removes it.

Hooks

server.lua
-- block or rewrite messages
KojaChat.AddHook('beforeSend', function(ctx)
    -- ctx = { source, channel, text, style, command, image }
    if ctx.channel == 'ooc' and GetEntityHealth(GetPlayerPed(ctx.source)) <= 0 then
        return false, 'You cannot use OOC while dead.'
    end
    return ctx.text:gsub('pineapple pizza', 'proper pizza')
end)

KojaChat.AddHook('afterSend', function(ctx, recipients)
    print(('%s wrote on %s to %d players'):format(ctx.source, ctx.channel, #recipients))
end)

beforeSend return values: false, reason blocks the message (reason is a locale key or plain text), a string replaces the text, anything else lets it through. AddHook returns a hook id for RemoveHook(id), or false for an unknown event. Hooks run for messages that go through a channel (player messages, channel commands and SendAs). They do not run for SendMessage, Broadcast, private messages or reports. An error inside a hook is printed to the console and skipped.

Badges

server.lua
-- shown before the name on every message until removed or the player leaves
KojaChat.SetBadge(source, 'vip', { label = 'VIP', color = '#facc15' })
KojaChat.RemoveBadge(source, 'vip')
KojaChat.GetBadges(source)

GetBadges returns the badges set through this API. Badges from Custom.GetBadges are added when a message is built.

Pin and polls

server.lua
KojaChat.Pin('Event tonight at the airport!', { author = 'Staff' })
KojaChat.Unpin()
KojaChat.GetPin()   -- { text, author, color, time } or nil

local pollId = KojaChat.CreatePoll({
    question = 'Next event?',
    options = { 'Car meet', 'Drag race', 'Heist night' },
    duration = 120,     -- seconds, at least 10
    channel = 'ooc',    -- optional, only players who see this channel can vote
})
KojaChat.GetPoll(pollId)   -- { id, question, options = { { label, votes } }, total, closed, author, left }
KojaChat.ClosePoll(pollId)

A poll needs a question and at least 2 options, and shows at most 6. CreatePoll returns nil when it is invalid. One vote per license, and a player can change it. Results stay available to GetPoll for 10 minutes after the poll closes.

Moderation and info

FunctionDescription
Mute(target, minutes?, reason?)Mutes an online player. 0 or nil minutes = permanent. Returns false when the player is offline
Unmute(target)Removes the mute
IsMuted(target)Returns muted, { reason, by, secondsLeft }
SetSlowmode(seconds)0 = off
GetPlayerInfo(source){ id, name, player, job, jobLabel, grade, gradeLabel, onDuty, department, admin }. Cached for 10 seconds
DeleteMessage(id)Removes a message for everyone

Client API

FunctionDescription
AddMessage(message)A local message only this player sees. Same format as SendMessage
AddSystem(text, opts?)Local system line
AddSuggestion(name, help, params?)Command suggestion. params = { { name = 'id', help = 'Player id' } }
RemoveSuggestion(name)Removes a suggestion
Open(prefill?)Opens the chat, optionally with text typed in, for example KojaChat.Open('/me ') from a radial menu. Returns false when it cannot open
Close()Closes the chat
IsOpen()Is the chat open
SetHidden(state)Hides the whole chat (cinematic mode, HUD toggle). Also TriggerEvent('koja-chat:client:setHidden', true)
IsHidden()Is the chat hidden
Clear()Clears the chat
Ignore(id), Unignore(id), IsIgnored(id)Hide the messages of a player for this player, until reconnect
SetSettings(settings)Changes this player's chat look: { theme, accent, position, scale, width, opacity, timestamps, fade, sounds, compact }

To block opening (handcuffed, dead, in a cutscene) return false from CustomClient.CanOpen in editable/client.lua. See Configuration.

Events

Listen with AddEventHandler from any resource.

Server

EventArguments
koja-chat:messagectx = { id, source, channel, text, style, command, image }, recipients. For messages that go through a channel
koja-chat:pollVotepollId, source, optionIndex
koja-chat:pollEndedpollId, results (the same as GetPoll)
koja-chat:messageDeletedid, by (player id, or nil when deleted from code)
server.lua
AddEventHandler('koja-chat:pollEnded', function(id, results)
    print(('Poll %d: %s got %d votes'):format(id, results.options[1].label, results.options[1].votes))
end)

Client

EventArguments
koja-chat:client:openednone
koja-chat:client:closednone
koja-chat:client:receivedmsg, every message this player receives
koja-chat:client:setHiddenstate. Trigger it to hide or show the chat

Default chat compatibility

Default chatKoja Chat
TriggerClientEvent('chat:addMessage', id, { args = {...}, color = {...}, template = ... })Rendered with colour codes and templates
chat:addSuggestion, chat:addSuggestions, chat:removeSuggestion, chat:clear, chat:addTemplateSupported
chatMessage on client and server, CancelEvent() worksSupported. Also fired for unknown /commands
exports.chat:addMessage (client and server)Supported through provide 'chat'
exports.chat:addSuggestion, removeSuggestion (client)Supported
^1 to ^9, ^*, ^_, ^~, ^r colour codesSupported
registerMessageHook, registerMode, unregisterModeExist so scripts do not error, but do nothing

A message sent with chat:addMessage in the Koja Chat format (a text and no args or template) is shown as a normal message.