Commit3e183a7aRecorded25 Feb 2026Repositorysigil-xmpp
Add procedure specs to xmpp and irc packages
Changed
src/sigil/xmpp.sgl | 1 +
src/sigil/xmpp/connection.sgl | 14 ++++++++++++++
src/sigil/xmpp/muc.sgl | 8 ++++++++
src/sigil/xmpp/presence.sgl | 4 ++++
src/sigil/xmpp/roster.sgl | 9 +++++++++
src/sigil/xmpp/sasl.sgl | 6 ++++++
src/sigil/xmpp/stanza.sgl | 17 +++++++++++++++++
7 files changed, 59 insertions(+)Diff
src/sigil/xmpp.sglmodified
@@ -90,6 +90,7 @@
90
;;; (xmpp-connect conn) 91
;;; ``` 92
(define (xmpp-install-features conn)+93
(: xmpp-connection? -> void?) 94
;; Handle roster pushes (IQ set with jabber:iq:roster) 95
(xmpp-on conn 'iq 96
(lambda (stanza)src/sigil/xmpp/connection.sglmodified
@@ -110,6 +110,7 @@
110
(jid #f) 111
(password #f) 112
(resource "sigil")))+113
(: (server: any?) (port: integer?) (jid: any?) (password: any?) (resource: string?) -> xmpp-connection?) 114
(unless server 115
(error "make-xmpp-connection: server: is required")) 116
(unless jid@@ -125,6 +126,7 @@
126
127
;;; Check if connection is in connected state. 128
(define (xmpp-connected? conn)+129
(: xmpp-connection? -> boolean?) 130
(eq? (xmpp-connection-state conn) 'connected)) 131
132
@@ -184,6 +186,7 @@
186
187
;;; Register a handler for all incoming stanzas. 188
(define (xmpp-on-stanza conn handler)+189
(: xmpp-connection? procedure? -> void?) 190
(set-xmpp-connection-stanza-handlers! 191
conn 192
(cons handler (xmpp-connection-stanza-handlers conn))))@@ -193,6 +196,7 @@
196
;;; Event types: 'message, 'presence, 'iq, 'connected, 197
;;; 'disconnected, 'error 198
(define (xmpp-on conn event handler)+199
(: xmpp-connection? symbol? procedure? -> void?) 200
(let* ((handlers (xmpp-connection-event-handlers conn)) 201
(existing (dict-ref handlers event '()))) 202
(set-xmpp-connection-event-handlers!@@ -221,6 +225,7 @@
225
;;; (display (message-body stanza))))) 226
;;; ``` 227
(define (xmpp-channel conn event)+228
(: xmpp-connection? symbol? -> any?) 229
(let* ((broadcasts (xmpp-connection-broadcasts conn)) 230
(bc (dict-ref broadcasts event #f))) 231
;; Create broadcast if it doesn't exist yet@@ -265,10 +270,12 @@
270
;;; (xmpp-send conn (xmpp-message to: "[email protected]" body: "Hi")) 271
;;; ``` 272
(define (xmpp-send conn stanza)+273
(: xmpp-connection? list? -> void?) 274
(conn-write conn (stanza->xml stanza))) 275
276
;;; Send raw XML string to the server. 277
(define (xmpp-send-raw conn data)+278
(: xmpp-connection? string? -> void?) 279
(conn-write conn data)) 280
281
;;; Send an IQ stanza and register a callback for the response.@@ -281,6 +288,7 @@
288
;;; (lambda (response) (display "Got roster\n"))) 289
;;; ``` 290
(define (xmpp-send-iq conn stanza callback)+291
(: xmpp-connection? list? procedure? -> void?) 292
(let ((id (stanza-id stanza))) 293
(when id 294
(set-xmpp-connection-iq-callbacks!@@ -291,6 +299,7 @@
299
300
;;; Send initial presence to indicate availability. 301
(define (xmpp-send-presence conn . args)+302
(: xmpp-connection? any? ... -> void?) 303
(xmpp-send conn (apply xmpp-presence args))) 304
305
@@ -305,6 +314,7 @@
314
;;; 315
;;; Returns #t on success, #f on failure. 316
(define (xmpp-connect conn)+317
(: xmpp-connection? -> boolean?) 318
(when (not (eq? (xmpp-connection-state conn) 'disconnected)) 319
(error "xmpp-connect: already connected or connecting")) 320
@@ -556,6 +566,7 @@
566
567
;;; Disconnect from the XMPP server. 568
(define (xmpp-disconnect conn)+569
(: xmpp-connection? -> void?) 570
(when (conn-socket conn) 571
;; Send stream close 572
(when (memq (xmpp-connection-state conn) '(connected authenticated binding))@@ -582,6 +593,7 @@
593
;;; 594
;;; Returns #t if connection is alive, #f if disconnected. 595
(define (xmpp-process-input conn)+596
(: xmpp-connection? -> boolean?) 597
(let ((data (conn-read conn))) 598
(cond 599
((or (not data) (eof-object? data))@@ -608,6 +620,7 @@
620
;;; 621
;;; Returns #t if connection is alive, #f if disconnected. 622
(define (xmpp-tick conn)+623
(: xmpp-connection? -> boolean?) 624
(let ((sock (conn-socket conn))) 625
(if (not sock) 626
#f@@ -622,6 +635,7 @@
635
;;; `with-async` context, cooperates with other tasks via `await-readable`. 636
;;; Otherwise blocks in a traditional event loop. 637
(define (xmpp-run conn)+638
(: xmpp-connection? -> void?) 639
(let ((sock (conn-socket conn))) 640
(when sock 641
(if (current-scheduler)src/sigil/xmpp/muc.sglmodified
@@ -89,6 +89,7 @@
89
;;; (xmpp-muc-join conn "[email protected]" "mynick") 90
;;; ``` 91
(define (xmpp-muc-join conn room-jid nick)+92
(: xmpp-connection? string? string? -> void?) 93
;; Register room 94
(let* ((rooms (get-muc-rooms conn)) 95
(room (muc-room jid: room-jid nick: nick)))@@ -102,6 +103,7 @@
103
104
;;; Leave a MUC room. 105
(define (xmpp-muc-leave conn room-jid (keys: (reason #f)))+106
(: xmpp-connection? string? (reason: any?) -> void?) 107
(let ((room (get-room conn room-jid))) 108
(when room 109
(xmpp-send conn@@ -121,17 +123,20 @@
123
;;; (xmpp-muc-message conn "[email protected]" "Hello room!") 124
;;; ``` 125
(define (xmpp-muc-message conn room-jid body)+126
(: xmpp-connection? string? string? -> void?) 127
(xmpp-send conn 128
(xmpp-message to: room-jid type: "groupchat" body: body))) 129
130
;;; Send a private message to a MUC occupant. 131
(define (xmpp-muc-private-message conn room-jid nick body)+132
(: xmpp-connection? string? string? string? -> void?) 133
(xmpp-send conn 134
(xmpp-message to: (string-append room-jid "/" nick) 135
type: "chat" body: body))) 136
137
;;; Set the subject of a MUC room. 138
(define (xmpp-muc-set-subject conn room-jid subject)+139
(: xmpp-connection? string? string? -> void?) 140
(xmpp-send conn 141
(xmpp-message to: room-jid type: "groupchat" subject: subject))) 142
@@ -139,6 +144,7 @@
144
;;; 145
;;; Returns a list of muc-occupant records, or '() if not in the room. 146
(define (xmpp-muc-occupants conn room-jid)+147
(: xmpp-connection? string? -> list?) 148
(let ((room (get-room conn room-jid))) 149
(if room 150
(dict-values (muc-room-occupants room))@@ -146,6 +152,7 @@
152
153
;;; Kick an occupant from a MUC room (requires moderator role). 154
(define (xmpp-muc-kick conn room-jid nick (keys: (reason #f)))+155
(: xmpp-connection? string? string? (reason: any?) -> void?) 156
(xmpp-send conn 157
(xmpp-iq type: "set" to: room-jid 158
children: (list@@ -157,6 +164,7 @@
164
165
;;; Send a mediated invitation to a user. 166
(define (xmpp-muc-invite conn room-jid invitee-jid (keys: (reason #f)))+167
(: xmpp-connection? string? string? (reason: any?) -> void?) 168
(xmpp-send conn 169
(xmpp-message to: room-jid 170
children: (listsrc/sigil/xmpp/presence.sglmodified
@@ -51,11 +51,13 @@
51
;;; (xmpp-set-status conn show: "away" status: "Be right back") 52
;;; ``` 53
(define (xmpp-set-status conn (keys: (show #f) (status #f) (priority #f)))+54
(: xmpp-connection? (show: any?) (status: any?) (priority: any?) -> void?) 55
(xmpp-send conn 56
(xmpp-presence show: show status: status priority: priority))) 57
58
;;; Go offline (send unavailable presence and disconnect). 59
(define (xmpp-go-offline conn)+60
(: xmpp-connection? -> void?) 61
(xmpp-send conn (xmpp-presence type: "unavailable")) 62
(xmpp-disconnect conn)) 63
@@ -63,6 +65,7 @@
65
;;; 66
;;; Returns a presence-info record or #f. 67
(define (xmpp-presence-of conn jid)+68
(: xmpp-connection? string? -> any?) 69
(let ((presences (dict-ref (xmpp-connection-event-handlers conn) 70
'%presences #f))) 71
(and presences (dict-ref presences jid #f))))@@ -71,6 +74,7 @@
74
;;; 75
;;; Returns a list of full JID strings that are currently available. 76
(define (xmpp-resources-of conn bare-jid)+77
(: xmpp-connection? string? -> list?) 78
(let ((presences (dict-ref (xmpp-connection-event-handlers conn) 79
'%presences #f))) 80
(if (not presences)src/sigil/xmpp/roster.sglmodified
@@ -85,6 +85,7 @@
85
;;; items))) 86
;;; ``` 87
(define (xmpp-request-roster conn callback)+88
(: xmpp-connection? procedure? -> void?) 89
(xmpp-send-iq conn 90
(xmpp-iq type: "get" 91
children: (list '(query (@ (xmlns "jabber:iq:roster")))))@@ -98,12 +99,14 @@
99
;;; 100
;;; Returns #f if the roster hasn't been fetched yet. 101
(define (xmpp-roster conn)+102
(: xmpp-connection? -> any?) 103
;; Roster is stored in the connection's event handlers dict 104
;; under the '%roster key 105
(dict-ref (xmpp-connection-event-handlers conn) '%roster #f)) 106
107
;;; Look up a specific contact in the cached roster. 108
(define (xmpp-roster-item conn jid)+109
(: xmpp-connection? string? -> any?) 110
(let ((roster (xmpp-roster conn))) 111
(and roster 112
(let loop ((items roster))@@ -119,6 +122,7 @@
122
;;; (xmpp-roster-add conn "[email protected]" name: "Friend") 123
;;; ``` 124
(define (xmpp-roster-add conn jid (keys: (name #f) (groups '())))+125
(: xmpp-connection? string? (name: any?) (groups: list?) -> void?) 126
(let ((children 127
(append 128
(if name@@ -133,6 +137,7 @@
137
138
;;; Remove a contact from the roster. 139
(define (xmpp-roster-remove conn jid)+140
(: xmpp-connection? string? -> void?) 141
(xmpp-send conn 142
(xmpp-iq type: "set" 143
children: (list@@ -146,18 +151,22 @@
151
152
;;; Send a subscription request to a JID. 153
(define (xmpp-subscribe conn jid)+154
(: xmpp-connection? string? -> void?) 155
(xmpp-send conn (xmpp-presence to: jid type: "subscribe"))) 156
157
;;; Accept a subscription request from a JID. 158
(define (xmpp-accept-subscription conn jid)+159
(: xmpp-connection? string? -> void?) 160
(xmpp-send conn (xmpp-presence to: jid type: "subscribed"))) 161
162
;;; Deny a subscription request from a JID. 163
(define (xmpp-deny-subscription conn jid)+164
(: xmpp-connection? string? -> void?) 165
(xmpp-send conn (xmpp-presence to: jid type: "unsubscribed"))) 166
167
;;; Unsubscribe from a JID's presence. 168
(define (xmpp-unsubscribe conn jid)+169
(: xmpp-connection? string? -> void?) 170
(xmpp-send conn (xmpp-presence to: jid type: "unsubscribe"))) 171
172
src/sigil/xmpp/sasl.sglmodified
@@ -62,6 +62,7 @@
62
;;; (define scram (make-scram-sha1 "user" "password")) 63
;;; ``` 64
(define (make-scram-sha1 username password)+65
(: string? string? -> vector?) 66
(let ((state (make-vector 8 #f)) 67
(nonce (base64-encode (random-bytes 18)))) 68
(vector-set! state 0 username)@@ -76,6 +77,7 @@
77
;;; 78
;;; Returns the base64-encoded message to send in the SASL auth element. 79
(define (scram-initial-message state)+80
(: vector? -> string?) 81
(let* ((username (vector-ref state 0)) 82
(nonce (vector-ref state 2)) 83
(bare (string-append "n=" username ",r=" nonce)))@@ -105,6 +107,7 @@
107
;;; Takes the base64-encoded server challenge, returns the base64-encoded 108
;;; client-final-message. 109
(define (scram-challenge-response state server-challenge-b64)+110
(: vector? string? -> string?) 111
(let* ((server-msg (utf8->string (base64-decode server-challenge-b64))) 112
(parts (parse-scram-challenge server-msg)) 113
(server-nonce (cdr (assoc "r" parts)))@@ -150,6 +153,7 @@
153
;;; 154
;;; Returns #t if the server signature matches, #f otherwise. 155
(define (scram-verify-server state server-final-b64)+156
(: vector? string? -> boolean?) 157
(let* ((server-msg (utf8->string (base64-decode server-final-b64))) 158
(parts (parse-scram-challenge server-msg)) 159
(server-sig-b64 (cdr (assoc "v" parts)))@@ -165,6 +169,7 @@
169
;;; 170
;;; Format: base64(\0username\0password) 171
(define (sasl-plain-response username password)+172
(: string? string? -> string?) 173
(let* ((user-bv (string->utf8 username)) 174
(pass-bv (string->utf8 password)) 175
(zero (make-bytevector 1 0))@@ -183,6 +188,7 @@
188
;;; 189
;;; Returns a symbol: 'scram-sha-1, 'plain, or #f if none supported. 190
(define (select-sasl-mechanism offered)+191
(: list? -> any?) 192
(let loop ((prefs mechanism-priority)) 193
(if (null? prefs) 194
#fsrc/sigil/xmpp/stanza.sglmodified
@@ -64,6 +64,7 @@
64
;;; ; => #<jid local: "user" domain: "example.com" resource: "bot"> 65
;;; ``` 66
(define (parse-jid str)+67
(: string? -> jid?) 68
(let ((at-pos (string-index str (lambda (c) (char=? c #\@)))) 69
(slash-pos (string-index str (lambda (c) (char=? c #\/))))) 70
(cond@@ -91,6 +92,7 @@
92
;;; ; => "[email protected]/bot" 93
;;; ``` 94
(define (jid->string j)+95
(: jid? -> string?) 96
(let ((local (jid-local j)) 97
(domain (jid-domain j)) 98
(resource (jid-resource j)))@@ -110,6 +112,7 @@
112
;;; (jid-bare (parse-jid "[email protected]/bot")) ; => "[email protected]" 113
;;; ``` 114
(define (jid-bare j)+115
(: any? -> string?) 116
(let ((j (if (string? j) (parse-jid j) j))) 117
(let ((local (jid-local j)) 118
(domain (jid-domain j)))@@ -128,6 +131,7 @@
131
;;; 132
;;; Uses a counter combined with random bytes for uniqueness. 133
(define (generate-stanza-id)+134
(: -> string?) 135
(set! id-counter (+ id-counter 1)) 136
(string-append "s" (number->string id-counter) "-" 137
(base64-encode (random-bytes 6))))@@ -160,6 +164,7 @@
164
(define (xmpp-message (keys: (to #f) (from #f) (type "chat") 165
(id #f) (body #f) (subject #f) 166
(children '())))+167
(: (to: any?) (from: any?) (type: string?) (id: any?) (body: any?) (subject: any?) (children: list?) -> list?) 168
(let ((id (or id (generate-stanza-id)))) 169
(append 170
(cons 'message@@ -176,6 +181,7 @@
181
(define (xmpp-presence (keys: (to #f) (from #f) (type #f) 182
(id #f) (show #f) (status #f) 183
(priority #f) (children '())))+184
(: (to: any?) (from: any?) (type: any?) (id: any?) (show: any?) (status: any?) (priority: any?) (children: list?) -> list?) 185
(append 186
(cons 'presence 187
(build-attrs 'to to 'from from 'type type 'id id))@@ -194,6 +200,7 @@
200
;;; ``` 201
(define (xmpp-iq (keys: (to #f) (from #f) (type "get") 202
(id #f) (children '())))+203
(: (to: any?) (from: any?) (type: string?) (id: any?) (children: list?) -> list?) 204
(let ((id (or id (generate-stanza-id)))) 205
(append 206
(cons 'iq@@ -207,6 +214,7 @@
214
215
;;; Get the type of a stanza (message, presence, iq) as a symbol. 216
(define (stanza-type stanza)+217
(: list? -> any?) 218
(if (pair? stanza) (car stanza) #f)) 219
220
;;; Get an attribute value from a stanza.@@ -215,18 +223,22 @@
223
;;; (stanza-attr msg 'to) ; => "[email protected]" 224
;;; ``` 225
(define (stanza-attr stanza name)+226
(: list? symbol? -> any?) 227
(sxml-attr-ref stanza name)) 228
229
;;; Get the 'to' attribute of a stanza. 230
(define (stanza-to stanza)+231
(: list? -> any?) 232
(sxml-attr-ref stanza 'to)) 233
234
;;; Get the 'from' attribute of a stanza. 235
(define (stanza-from stanza)+236
(: list? -> any?) 237
(sxml-attr-ref stanza 'from)) 238
239
;;; Get the 'id' attribute of a stanza. 240
(define (stanza-id stanza)+241
(: list? -> any?) 242
(sxml-attr-ref stanza 'id)) 243
244
;;; Get the body text from a message stanza.@@ -236,6 +248,7 @@
248
;;; ; => "Hello" 249
;;; ``` 250
(define (message-body stanza)+251
(: list? -> any?) 252
(let ((body-el (stanza-child stanza 'body))) 253
(if body-el 254
(sxml-text body-el)@@ -247,6 +260,7 @@
260
;;; (stanza-child msg 'body) ; => (body "Hello") 261
;;; ``` 262
(define (stanza-child stanza tag)+263
(: list? symbol? -> any?) 264
(let ((content (sxml-content stanza))) 265
(let loop ((items content)) 266
(if (null? items)@@ -258,6 +272,7 @@
272
273
;;; Find all child elements with the given tag name. 274
(define (stanza-children stanza tag)+275
(: list? symbol? -> list?) 276
(let ((content (sxml-content stanza))) 277
(let loop ((items content) (result '())) 278
(if (null? items)@@ -271,6 +286,7 @@
286
;;; 287
;;; Returns the concatenation of all string children, or #f if none. 288
(define (sxml-text element)+289
(: any? -> any?) 290
(if (not (pair? element)) 291
#f 292
(let ((content (sxml-content element)))@@ -296,6 +312,7 @@
312
;;; ; => "<message to=\"[email protected]\" ...><body>Hi</body></message>" 313
;;; ``` 314
(define (stanza->xml stanza)+315
(: list? -> string?) 316
(sxml->xml stanza)) 317
318
))