~aleteoryx/tclircc-docs

tclircc-docs/irc.tcl.md -rw-r--r-- 14.2 KiB
52479f5cAleteoryx update for commit 614b00a3 a month ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
# irc.tcl

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.


# Getting a channel

`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](#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](#channel-metadata).


# Listening to it

`irc.tcl` provides an event dispatch system, via a **`fileevent`**
script registered on the IRC channel. Events are dispatched by matching
their [patterns](#message-pattern-lists) 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 either a
sub-interpreter or a seperate thread. Interpreter listeners should
yield themselves with **`after`** during long operations, and never
block for extended periods. If that is infeasible, threaded listeners
are recommended.

`listener`-type scripts are spawned with the variable `dispatch` set to
the channel they are to recieve dispatches over. Each line will be a
**`dict`** with contents as described in
[Event Dispatch Contents](#event-dispatch-contents).
Interpreters are given access to the
[Dispatch-aliased IRC commands](dispatch-aliased-irc-commands), while
threads are given the variable `parent`, the ID of the main thread.

When a listener is removed, it will recieve a message of just `end`. It
should perform necessary cleanup quickly, as the application is likely
exiting. Threads may simply **`thread::release`** themselves, while
interps may call the provided **`selfdestruct`**.

### `irc::listener add `*`chan ?-thread? patlist script`*

Registers `script` as a listener-type handler on `chan`, matching
`patlist` as [described below](#message-pattern-lists). Returns an id
that can be passed to **`irc::listener remove`** or **`irc::patlist`**.

If `-thread` is passed, it will be created as a threaded listener,
otherwise it will be created in a sub-interpreter.

### `irc::listener remove `*`chan id`*

Unregisters the listener identified by `id` from `chan`.

Ignores requests for nonexistent handlers or handlers of the wrong type.


## `irc::handler `*`subcommand chan ?arg ...?`*

Configure handler-type event handlers.

Handler-type event handlers execute a script everytime a message is
matched. These scripts can be executed in either the global scope, a
sub-interpreter, or a seperate thread.

`handler`-type scripts are spawned with the variable `dispatch` set as
described in
[Event Dispatch Contents](#event-dispatch-contents).
Interpreters are given access to the
[Dispatch-aliased `irc` commands](dispatch-aliased-codeirccode-commands),
while threads are given the variable `parent`, the ID of the main
thread.

When a handler is removed, if it has an interpreter or thread, 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.

It is essentially assumed that an individual `handler` will have
exclusive ownership of its sub-interpreter or thread.

### `irc::handler add `*`chan ?-thread? patlist script ?interp-or-thread?`*

Registers `script` as a handler-type handler on `chan`, matching
`patlist` as [described below](#message-pattern-lists). Returns an id
that can be passed to **`irc::extern remove`** or **`irc::patlist`**.

If neither `-thread` nor `interp-or-thread` are set, `script` will be
executed in the global scope. If `interp-or-thread` is set, it is the
ID of the interpreter to execute the script in. If `-thread` is set,
`script` is executed in a persistent seperate thread. If `-thread` and
`interp-or-thread` are set, `interp-or-thread` is the ID of the thread
to use.

### `irc::handler remove `*`chan id`*

Unregisters the handler-type event handler identified by `id` from `chan`.

Ignores requests for nonexistent handlers or handlers of the wrong type.


## `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::extern 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](#message-pattern-lists). 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`.

Ignores requests for nonexistent handlers or handlers of the wrong type.


## `irc::patlist `*`chan id ?patlist?`*

Get or set the [message pattern list](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

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.

### Message Pattern List Examples:

```tcl
# 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
```


## Event Dispatch Contents

The event dispatch dictionary contains the following properties:

- `rawmsg`: the raw IRC message
- `chan`: the source channel
- `tags`: the tags portion of the message
- `src`: the source portion of the message
- `srctype`: the type of message source, either `servername` or `user`
- `srcparts`: the `srcparts` dict, returned from **`irc::src parse`**
- `cmd`: the command of the message
- `params`: the params of the message


## Dispatch-aliased `irc` Comands

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


# Channel Metadata

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


# Parsing

`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](https://modern.ircdocs.horse/) and
[Message Tags](https://ircv3.net/specs/extensions/message-tags.html)
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}`