session:chat: The Chat Channels
The channels one character’s chat holds, which one is on screen, putting another there, and saying a line. Reach it through the session of the character you mean. session:chat() is one collection over the tabs down the side of the chat window.
local session = hafen.session():current() -- the character on screen
for _, channel in ipairs(session and session:chat():list() or {}) do
hafen.log():write((channel:name() or "?") .. " " .. channel:kind() .. " urgency " .. channel:urgency())
end
| Rule |
Detail |
| Whose chat |
The chat is a window of one login’s HUD: two characters have two Party channels, two System logs, two private conversations with the same person. hafen.session():get("alt"):chat():count() answers for that character. |
| One object |
session:chat() is the same object every call, minted once per session. A character with no HUD yet has an empty collection and no selection. |
Read
| Method |
Returns |
Permission |
Description |
session:chat():list(filter) |
Channel[] |
Unprotected |
Every channel, in tab order. |
session:chat():count(filter) |
number |
Unprotected |
How many match. |
session:chat():find(filter) |
Channel \| nil |
Unprotected |
The first that matches. |
session:chat():selected() |
Channel \| nil |
Unprotected |
The channel on screen. nil before the HUD is up, and until another is picked after the selected one goes. |
| Rule |
Detail |
filter |
The filter matches a channel’s name. A predicate receives the Channel. Nothing throws. Nothing is protected. |
No session:chat():get |
A channel carries no key the client addresses: the server places and removes tabs, and two may carry one name. session:chat():find(needle) searches by name, session:chat():list()[n] takes a position, and get raises naming both. |
A channel
| Method |
Returns |
Permission |
Description |
channel:name() |
string \| nil |
Unprotected |
The caption on its tab. |
channel:kind() |
string \| nil |
Unprotected |
The kind of line it holds. |
channel:urgency() |
number \| nil |
Unprotected |
How loudly it is unread. 0 when nothing is. |
channel:exists() |
boolean |
Unprotected |
Whether it is still one of that character’s. Always answers. |
channel:message() |
collection |
Unprotected |
The lines it has shown. |
channel:info() |
Channel \| nil |
Unprotected |
A plain-table snapshot. |
| Rule |
Detail |
| Interned on the tab |
session:chat():list()[1] == session:chat():selected() when it is the one on screen. seen[channel] = true works. Two characters’ Party channels are two objects. channel:message() is a view minted per call. The identity is on the lines. |
| A gone channel answers only its identity |
The server closes a private conversation by taking its tab: channel:exists() goes false and every other read answers nil. The object stays the key your table is under, what a ChannelRemoved handler matches on. Index what you want to know while the channel is there. |
| A new private conversation is nameless for a moment |
The client names the tab after the other person through this character’s kin roster. Read the name when you use it. |
The kinds
channel:kind() is the site key the channel’s lines are coloured at: what a theme paints and what your addon reads are one closed set.
| Kind |
The channel |
"chat" |
An ordinary channel: Area Chat, a village or realm channel, anything the server names. |
"chat.system" |
The System log: what the client tells you. |
"chat.party" |
The Party channel. |
"chat.private" |
One private conversation, both halves. |
channel:urgency() is the level the client keeps for an unread tab, what chat.urgent’s palette is read one entry per, rising. 0 is nothing unread and takes no colour.
The lines
channel:message() is what the channel has shown, oldest first.
local channel = hafen.session():current():chat():selected()
local newest = channel and channel:message():get(channel:message():count())
if newest then
local speaker_name = newest:speaker() and newest:speaker():name() or "-"
hafen.log():write(speaker_name .. ": " .. (newest:text() or ""))
end
| Method |
Returns |
Permission |
Description |
channel:message():list(filter) |
Message[] |
Unprotected |
Every line, oldest first. Copies the whole scrollback, large on a long login. |
channel:message():count(filter) |
number |
Unprotected |
How many match. |
channel:message():find(filter) |
Message \| nil |
Unprotected |
The first that matches. |
channel:message():get(index) |
Message \| nil |
Unprotected |
The line at position index. nil past the newest. |
| Rule |
Detail |
filter |
Matches a line’s own text, not the speaker’s name. A predicate receives the Message. |
| A position is a real address |
The client keeps every line while the channel is open: line 40 is line 40 for the login, and :get(channel:message():count()) is the newest. Read the count and address the lines you want. |
| Indices start at one |
:get(0), a negative and a fraction raise. Past the newest is nil, since the scrollback grows while you read. |
A line
| Method |
Returns |
Permission |
Description |
message:text() |
string \| nil |
Unprotected |
The line as the client draws it, markup quoted. |
message:raw() |
string \| nil |
Unprotected |
The line as it arrived, quoting still to do. |
message:kind() |
string \| nil |
Unprotected |
The site key this line is drawn at. |
message:color() |
colour \| nil |
Unprotected |
The colour the line carries of itself before any theme. The server’s for a party line, the client’s for your own. nil for the channel’s ordinary colour. |
message:time() |
number \| nil |
Unprotected |
When the client took the line, epoch seconds with a fraction: print with string.format("%d", message:time()), since tostring gives scientific notation. |
message:speaker() |
Kin \| nil |
Unprotected |
Who said it, a Kin of that character’s roster. nil where the line names nobody (the System log, an ordinary channel’s plain lines, both halves of a private conversation). |
message:mine() |
boolean \| nil |
Unprotected |
Whether this character said it. A test of which kind of line the client built, asked of every line. false for the System log’s own output. |
message:channel() |
Channel \| nil |
Unprotected |
The channel it landed in, the object you read it from. |
message:exists() |
boolean |
Unprotected |
Whether it is still readable. Always answers. |
message:info() |
Message \| nil |
Unprotected |
A plain-table snapshot. |
| Rule |
Detail |
| Interned on channel and position |
Two reads of one line are one object. seen[message] = true works. |
| A line goes when its channel does, and only then |
Closing a private conversation takes its scrollback: message:exists() goes false, every other read nil. A line scrolled out of sight reads back as written. |
| The quoting rule |
message:text() is the line as the chat drew it through the rich-text parser, with markup quoted. A $col{ff0000}{...} another player typed appears as those characters. Put it on a label, a tooltip or a HUD overlay and it draws as it drew in the chat. message:raw() is the bytes the server sent: the read for matching (if message:raw():match("^wtb ") then), and the one to keep away from anything that renders rich text. |
The kind of a line
message:kind() is its channel’s kind, one of the kinds, except for one line that names its own.
| Kind |
The line |
"chat.mine" |
A line this character said, in an ordinary channel or the Party channel. |
A channel of kind "chat" holds lines of kind "chat" and "chat.mine". Both halves of a private conversation are "chat.private", and message:mine() tells them apart.
Lines arriving
MessageAdded fires for every line arriving in any channel of any character, your own included (the client shows one once the server has taken it). It hands the Message, the object channel:message():get(index) answers, and the Session last. Lines that arrived before your addon loaded fire nothing. channel:message():list() reads them.
hafen.event():on("MessageAdded", function(message, session)
if message:kind() == "chat.private" and not message:mine() then
hafen.log():write(session:user() .. " was told: " .. (message:text() or ""))
end
end)
A line you say in this handler comes back to it. channel:send puts the line on the wire. The server hands it back as another MessageAdded to every handler, yours included. A handler that sends on every line sends on its own echo, one line a frame. message:mine() is false for everyone else’s line.
Write (unprotected)
| Method |
Returns |
Permission |
Description |
session:chat():selected(channel) |
the collection |
Unprotected |
Put channel on screen in that character’s chat, the gesture of clicking its tab. |
local session = hafen.session():current()
local party = session:chat():find("Party")
if party then session:chat():selected(party) end
| Rule |
Detail |
| Already on screen |
Changes nothing and fires no ChannelSelected. |
| Raises |
A value that is not a Channel. A channel that has gone (channel:exists()). A character whose HUD is not up. A channel belonging to another character (the collection you read it from is the one you may write it to). |
| No permission |
It moves the client’s own window. The server is never told. |
The keyboard needs ui.focus |
The client’s tab change puts the cursor in the channel’s entry line, and so does this one for an addon granted ui.focus. Without the key the tab changes and the keyboard stays where the player left it. |
Write (protected)
The client sends only shapes a player could compose.
| Method |
Returns |
Permission |
Description |
channel:send(text) |
the channel |
chat.send |
Say text in that channel, as that character. |
local alt_session = hafen.session():get("alt")
local area = alt_session:chat():find("Area Chat")
if area then area:send("on my way") end
| Rule |
Detail |
| Out of the channel’s login |
A character you are not looking at speaks for itself. |
| Raises, in this order |
The missing permission. An empty or non-string text. A channel that has gone or has no entry line (a "chat.system" channel refuses naming the kinds that take a line). |
text |
One typed line, 1 to 512 characters. A newline, tab or other control character raises naming its position. Sent exactly as written: the server decides what a line does (a command, an emote, a whisper). |
| Why protected |
Other players see it under your character’s name. The consent line is “say a line in the chat, as any of your characters”, covering every login. |
Channels coming and going
The chat events report a channel opened, closed or put on screen, each handing the Channel and the Session last. They report changes: an addon loaded with channels already open receives no event for them, and session:chat():list() reads what is there. A channel arriving is reported added before selected.
hafen.event():on("ChannelSelected", function(channel, session)
hafen.log():write(session:user() .. " is looking at " .. (channel:name() or "?"))
end)
See Also