# Developer API

## Using it

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

```lua title="your fxmanifest.lua"
shared_script '@koja-chat/import.lua'
```

```lua title="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

| 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`.

```lua title="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.

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

### Channels

```lua title="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,
})
```

| 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](/new/chat/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

```lua title="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

```lua title="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

```lua title="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

```lua title="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

| 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](/new/chat/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) |

```lua title="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

| 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.
