Developer API
Using it
Call the exports directly, or load import.lua to call the same functions without the colon.
shared_script '@koja-chat/import.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
| Function | Description |
|---|---|
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.
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.
local id = KojaChat.SendMessage(-1, { text = 'Server restart in 5 minutes', icon = 'clock' })
SetTimeout(60000, function() KojaChat.DeleteMessage(id) end)Channels
-- 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,
})| Function | Description |
|---|---|
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
-- 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
-- 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
-- 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
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
| Function | Description |
|---|---|
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
| Function | Description |
|---|---|
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
| Event | Arguments |
|---|---|
koja-chat:message | ctx = { id, source, channel, text, style, command, image }, recipients. For messages that go through a channel |
koja-chat:pollVote | pollId, source, optionIndex |
koja-chat:pollEnded | pollId, results (the same as GetPoll) |
koja-chat:messageDeleted | id, by (player id, or nil when deleted from code) |
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
| Event | Arguments |
|---|---|
koja-chat:client:opened | none |
koja-chat:client:closed | none |
koja-chat:client:received | msg, every message this player receives |
koja-chat:client:setHidden | state. Trigger it to hide or show the chat |
Default chat compatibility
| Default chat | Koja Chat |
|---|---|
TriggerClientEvent('chat:addMessage', id, { args = {...}, color = {...}, template = ... }) | Rendered with colour codes and templates |
chat:addSuggestion, chat:addSuggestions, chat:removeSuggestion, chat:clear, chat:addTemplate | Supported |
chatMessage on client and server, CancelEvent() works | Supported. 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 codes | Supported |
registerMessageHook, registerMode, unregisterMode | Exist 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.