The core protocol library. irc.tcl
exclusively handles the raw
stream protocol and event dispatch. All other responsibilities are
out-of-scope for it.
It exposes the irc
namespace, which contains the logic for
message-parsing, message validation, channel management, event
dispatch, and various utilities listed below.
irc.tcl
provides 2 ways to connect to an IRC server.
irc::connect
hostname port ?usetls?
Connects to hostname:port
, and sets up all the necessary state. If
usetls is set to true, the tls
module will be used to connect,
instead of the builtin socket
command. If unset, socket
will be used.
Channel metadata is initialized with proto
,
hostname
, port
, and uri
set.
irc::enroll
chan ?meta?
Sets up the internal state necessary for chan
to be
used as an IRC socket. It is called internally by irc::connect
.
This command is exposed for the use-case where an IRC channel might
have a more bespoke acquisition process than a simple socket
connection.
meta
is the initial state of the
channel metadata.
irc.tcl
provides an event dispatch system, via a fileevent
script registered on the IRC channel. Events are dispatched by matching
their patterns against incoming messages.
irc::listen
subcommand chan
Enable or disable the fileevent
script for the dispatch system.
irc::listen
on
chan
Apply the fileevent
wrapper to chan
. Returns the previous
fileevent
wrapper.
irc::listen
off
chan
Remove the fileevent
wrapper from chan
. Errors if it is not the
irc wrapper.
irc::listener
subcommand chan ?arg ...?
Configure listener-type event handlers.
Listener-type handlers are scripts, executed in a sub-interpreter.
The script is executed as part of event handling, and is expected to
return to the main interpreter via after
periodically. For
application responsiveness and stability reasons, it is expected that
listener scripts will not take longer than 100ms to execute, though
this is not enforced. If consistent long-running computations are
required, consider using irc::extern
.
listener
-type scripts are spawned with the variable chan
set to the
channel they recieve dispatches over. This will be a dict
with
contents as described in
Event Dispatch Contents.
They are given access to the
Dispatch-aliased IRC commands.
When a listener is removed, it will recieve a message of just end
. It
should perform necessary cleanup quickly, and return, as the
application is likely exiting, and it may not be re-executed if it
yields.
irc::listener add
chan patlist script
Registers script
as a listener-type handler on chan
, matching
patlist
as described below. Returns an id
that can be passed to irc::listener remove
or irc::patlist
.
irc::listener remove
chan id
Unregisters the listener identified by id
from chan
.
irc::handler
subcommand chan ?arg ...?
Configure handler-type event handlers.
Handler-type event handlers execute a script everytime a message is matched. For application responsiveness and stability reasons, it is expected that handler scripts will not take longer than 100ms to execute, though this is not enforced.
Handlers can be created with or without a stored interpreter. If created without, they will be spawned with a new interpreter for each message, and clean scope.
handler
-type scripts are spawned with the variable dispatch
set as
described in
Event Dispatch Contents.
They are given access to the
Dispatch-aliased irc
commands.
When a handler is removed, if it has a stored interpreter, it will be deleted. Applications with persistent state should take care to store it to disk after each command, or use one of the other dispatch types.
irc::handler add
chan patlist script ?interp?
Registers script
as a handler-type handler on chan
, matching
patlist
as described below. Returns an id
that can be passed to irc::extern remove
or irc::patlist
.
If interp
is supplied, script
will be added as a stored-interpreter
handler, with interp
as the stored-interpreter. The same alias setup
performed on internally created interpreters will be performed, once, on
the supplied interpreter, and dispatch
will be set just before
executing script
.
irc::extern remove
chan id
Unregisters the extern handler identified by id
from chan
.
irc::extern
subcommand chan ?arg ...?
Configure extern-type event handlers.
Extern-type event handlers are backed by a pair of channels. One is for
message dispatch, one is for message replying. Dispatch comes in pairs
of lines, the first being the source channel and the second being the
raw IRC message. Replies are just single IRC messages. Due to this
asymmetry, it is OK to reuse a dispatch channel for multiple extern
handlers, but not OK to reuse a reply channel. You will need to make
use of FIFOs or pipes for I/O multiplexing.
When an extern-type handler is removed, the channel id and end
will
be written to it. The dispatch pipe is never closed by irc.tcl
. The
reply pipe will be closed immediately. Ensure code that uses multiple
handlers accounts for this.
irc::handler add
chan patlist ochan ichan
Registers ochan
and ichan
as the dispatch and reply pipes of a
extern-type handler on chan
, matching patlist
as
described below. Returns an id that can
be passed to irc::extern remove
or irc::patlist
.
irc::extern remove
chan id
Unregisters the extern handler identified by id
from chan
.
irc::patlist
chan id ?patlist?
Get or set the message pattern list for handler
id
on chan
. If patlist
is supplied, it will override the current
one and return patlist
, otherwise it will return the current pattern
list.
Message pattern lists are lists of lists of string match
patterns. Messages are matched on the command and the first N-1 params,
where N is the length of the message pattern. If the first N segments
all match, the match succeeds. If a message is shorter than a pattern,
the match fails. If any of the message patterns in the message pattern
list for a handler match, the handler is called.
# pattern:
*
# messages:
PRIVMSG #general :what's up gamers
# matches
PRIVMSG #amehut :bot, do something
# matches
PRIVMSG #bot :do something
# matches
PING foo
# matches
# pattern:
{{}}
# messages:
PRIVMSG #general :what's up gamers
# doesn't match
PRIVMSG #amehut :bot, do something
# doesn't match
PRIVMSG #bot :do something
# doesn't match
PING foo
# doesn't match
# pattern:
{{PRIVMSG * {bot, *}} {PRIVMSG #bot}}
# messages:
PRIVMSG #general :what's up gamers
# doesn't match
PRIVMSG #amehut :bot, do something
# matches
PRIVMSG #bot :do something
# matches
PING foo
# doesn't match
# pattern:
PING
# messages:
PRIVMSG #general :what's up gamers
# doesn't match
PRIVMSG #amehut :bot, do something
# doesn't match
PRIVMSG #bot :do something
# doesn't match
PING foo
# matches
The event dispatch dictionary contains the following properties:
rawmsg
: the raw IRC messagechan
: the source channeltags
: the tags portion of the messagesrc
: the source portion of the messagesrctype
: the type of message source, either servername
or user
srcparts
: the srcparts
dict, returned from irc::src parse
cmd
: the command of the messageparams
: the params of the messageirc
ComandsIn listener
and handlers
, the following commands are aliased:
irc::esc
irc::extern
irc::handler
irc::is
irc::listener
irc::meta
irc::msg
irc::patlist
irc::src
irc::tags
irc::unesc
Additionally, the relevant channel is shared for direct writing.
Every channel is initialized with metadata when **enroll
**ed. This may be
empty, but it's still there. Metadata is backed with a dict.
irc::meta
subcommand chan ?arg ...?
A thin wrapper around dict
commands that routes them to the channel
metadata dict for chan
.
irc::meta read
chan
Returns the entire channel metadata dict for chan
.
irc::meta exists
chan key ?key ...?
A thin wrapper around dict exists
.
irc::meta get
chan ?key ...?
A thin wrapper around dict get
.
irc::meta set
chan key ?key ...? value
A thin wrapper around dict set
.
irc::meta unset
chan key ?key ...?
A thin wrapper around dict unset
.
irc.tcl
exposes a handful of interfaces for parsing IRC primitives.
irc::tags
subcommand ?arg ...?
An interface that allows you to treat an IRC tags string like a dict. O(n) time complexity, but realistically it will never handle a list of more than 3 or 4 so who cares.
All subcommands operate on values, not variables. Escaping and unescaping are handled transparently.
irc::tags create
?key value ...?
Creates an IRC tags string from the passed key-value pairs and returns it.
Errors if any key
includes forbidden characters.
irc::tags dict
tags
Converts the passed tags string to a dict with identical members.
irc::tags exists
tags key
Returns true if tags
includes they key key
. Returns false
otherwise.
irc::tags get
tags ?key?
If key
is set, returns the value associated with it, or an error if
it does not exist.
If key
is unset, returns a list of key-value pairs, similar to
dict get
.
irc::tags merge
?tags ...?
Merges all arguments into one tags string, and returns it. Each tags
may be either a tags string or a dict string.
Errors if any key includes forbidden characters.
irc::tags remove
tags key
Returns a new tags string without key
.
irc::tags set
tags key ?value?
Returns a new tags string with key
set to value
. If value is unset
or empty, the key is serialized alone, with no equals sign.
Errors if key
includes forbidden characters.
irc::src
subcommand ?arg ...?
IRC source parsing and generation utility.
irc::src parse
src ?partsVar?
Parses src
and returns either server
or user
, indicating the
type of src
. If partsVar
is specified, it will be set to a dict
containing, for user
values of src
, the nick
, username
, and
host
segments as entries, and for server
values, the servername
segment as an entry.
Errors if src
is not a valid IRC source string.
irc::src server
servername
Returns an IRC server source with servername
as its component.
Errors if servername
is not a valid servername.
irc::src user
*?-user user? ?-host host? nick
Returns an IRC user source with nick
as the nickname component. If
-user
is specified, the user
field will be inserted. If -host
is
specified, the host
field will be inserted.
Errors if any argument is invalid for its role.
irc::msg
subcommand ?arg ...?
IRC message formatting and parsing utility.
irc::msg fmt
?-tags tags? ?-src src? cmd ?arg ...?
Returns an IRC message string with cmd
as the command. All arguments
present are appended, and the final argument is always serialized as
trailing. If -tags
is specified, the tags
field will be inserted.
If -src
is specified, the src
field will be inserted.
Errors if any argument is invalid for its role.
irc::msg send
chan ?-tags tags? ?-src src? cmd ?arg ...?
Calls irc::msg fmt
internally, and sends the message over chan
.
irc::msg parse
message tagsVar srcVar cmdVar paramsVar
Parses the message in message
. tagsVar
will be set to the tags, if
present. srcVar
will be set to the source, if present. cmdVar
will
be set to the command. paramsVar
will be set to a list of parameters
in the message, if any.
irc::is
type value
A general validation utility for IRC strings. Returns true if value
is a valid string of type type
. Returns false otherwise.
For more details on how these are implemented, read the Modern IRC Client and Message Tags specifications.
Possible values of type
:
cmd
: an IRC command, e.g. PRIVMSG
or 421
.cmd::named
: a named IRC command, e.g. PRIVMSG
cmd::numeric
: a numeric IRC status code, e.g. 421
misc::dict
: a tcl dict, e.g. foo bar a 1 b 2 c 3 nested {di cts}
msg::param
: an IRC message parameter, e.g. #general
msg::trailing
: an IRC message trailing segment, e.g. hi guys! :D
nick
: an IRC nickname, e.g. aph
src
: an IRC source, e.g. aph!~alice@example.com
or
irc.example.com
src::user
: an IRC user source, e.g. aph!~alice@example.com
src::servername
: an IRC servername source, e.g. irc.example.com
src::part
: a non-nickname part of an IRC source, e.g. ~alice
tags
: a tags string, e.g.
account=aph;time=2024-08-03T23:36:00.320Z
tags::tag
: an IRC tag string, e.g. account=aph
tags::key
: an IRC tags key, e.g. account
tags::value
: an IRC tags value, e.g. aph
irc::esc
type value
Returns value
, escaped by the rules of type
.
Possible values of type
:
tags::value
: an IRC tags value, e.g. aph
or
int\smain\s()\n{\n\s\sputs("Hello,\sWorld!")\:\n\s\sreturn\s0\:\n}
irc::unesc
type value
Returns value
, unescaped by the rules of type
.
Possible values of type
:
tags::value
: an IRC tags value, e.g. aph
or
int\smain\s()\n{\n\s\sputs("Hello,\sWorld!")\:\n\s\sreturn\s0\:\n}