AtlatestRepositorysigil-xmpp
1# XMPP
2
3> XMPP client library with STARTTLS, SASL authentication, roster, presence, and MUC support.
4
5```scheme
6(import (sigil xmpp))
7```
8
9## Connecting
11```scheme
12(define conn (make-xmpp-connection
13 server: "example.com"
15 password: "secret"
16 resource: "bot"))
18;; Install roster, presence, and MUC tracking
19(xmpp-install-features conn)
21;; Connect (TCP -> STARTTLS -> SASL -> resource bind)
22(xmpp-connect conn)
24;; Announce availability
25(xmpp-send-presence conn)
27;; Run the event loop (blocks until disconnected)
28(xmpp-run conn)
29```
31`xmpp-connect` handles the full handshake: TCP connection, STARTTLS upgrade, SASL authentication (SCRAM-SHA-1 or PLAIN), and resource binding. Returns `#t` on success, `#f` on failure.
33`xmpp-run` processes incoming stanzas. Inside a `with-async` context it cooperates with other tasks via `await-readable`; otherwise it blocks with `socket-select`.
35## Sending Messages
37```scheme
38;; Chat message
39(xmpp-send conn
40 (xmpp-message to: "[email protected]" body: "Hello!"))
42;; Message with subject
43(xmpp-send conn
44 (xmpp-message to: "[email protected]"
45 body: "Check this out"
46 subject: "Interesting"))
47```
49## Handling Events (Callbacks)
51Register handlers for specific event types with `xmpp-on`:
53```scheme
54(xmpp-on conn 'message
55 (lambda (stanza)
56 (let ((body (message-body stanza))
57 (from (stanza-from stanza)))
58 (when (and body from)
59 (display (string-append from ": " body "\n"))))))
61(xmpp-on conn 'presence
62 (lambda (stanza)
63 (display (string-append (stanza-from stanza) " is now "
64 (or (stanza-attr stanza 'type) "available") "\n"))))
66(xmpp-on conn 'connected
67 (lambda (conn)
68 (display "Connected!\n")))
70(xmpp-on conn 'disconnected
71 (lambda (conn)
72 (display "Disconnected.\n")))
73```
75Event types: `message`, `presence`, `iq`, `stanza`, `connected`, `disconnected`, `error`.
77`xmpp-on-stanza` registers a handler for all incoming stanzas regardless of type.
79## Handling Events (Channels)
81For sequential, composable event processing use `xmpp-channel`:
83```scheme
84(import (sigil xmpp)
85 (sigil async)
86 (sigil channels))
88(with-async
89 (go (xmpp-run conn))
91 (let ((msgs (xmpp-channel conn 'message)))
92 (for-channel msgs
93 (lambda (stanza)
94 (let ((body (message-body stanza)))
95 (when body
96 (xmpp-send conn
97 (xmpp-message to: (stanza-from stanza)
98 body: (string-append "Echo: " body)))))))))
99```
101Channels work with `channel-receive`, `for-channel`, and `channel-select` for multiplexing:
103```scheme
104(with-async
105 (go (xmpp-run conn))
107 (let ((msgs (xmpp-channel conn 'message))
108 (pres (xmpp-channel conn 'presence)))
109 (let loop ()
110 (channel-select
111 (msgs => (lambda (s) (handle-message s)))
112 (pres => (lambda (s) (handle-presence s))))
113 (loop))))
114```
116## IQ Requests
118Send IQ stanzas with automatic response correlation:
120```scheme
121(xmpp-send-iq conn
122 (xmpp-iq type: "get"
123 to: "example.com"
124 children: (list '(query (@ (xmlns "http://jabber.org/protocol/disco#info")))))
125 (lambda (response)
126 (display "Got disco response\n")))
127```
129The callback is invoked once when the server replies with a matching IQ result or error.
131## Roster
133```scheme
134;; Fetch the roster
135(xmpp-request-roster conn
136 (lambda (items)
137 (for-each (lambda (item)
138 (display (string-append (roster-item-jid item) "\n")))
139 items)))
141;; Access cached roster (after fetch)
142(xmpp-roster conn) ; => list of roster-item, or #f
143(xmpp-roster-item conn "[email protected]") ; => roster-item or #f
145;; Add a contact
146(xmpp-roster-add conn "[email protected]" name: "Friend")
148;; Remove a contact
149(xmpp-roster-remove conn "[email protected]")
150```
152### Roster Item Accessors
154```scheme
155(roster-item-jid item) ; => "[email protected]"
156(roster-item-name item) ; => "Alice" or #f
157(roster-item-subscription item) ; => "both", "from", "to", "none"
158(roster-item-groups item) ; => ("Friends" "Work")
159```
161### Subscriptions
163```scheme
164(xmpp-subscribe conn "[email protected]")
165(xmpp-accept-subscription conn "[email protected]")
166(xmpp-deny-subscription conn "[email protected]")
167(xmpp-unsubscribe conn "[email protected]")
168```
170## Presence
172```scheme
173;; Send available presence
174(xmpp-send-presence conn)
176;; Set status
177(xmpp-set-status conn show: "away" status: "Be right back")
179;; Go offline and disconnect
180(xmpp-go-offline conn)
181```
183### Querying Presence
185With `xmpp-install-features`, presence is tracked automatically:
187```scheme
188;; Get presence for a full JID
189(xmpp-presence-of conn "[email protected]/mobile")
190; => #<presence-info jid: "..." show: "available" status: #f>
192;; Get all online resources for a bare JID
193(xmpp-resources-of conn "[email protected]")
194; => ("[email protected]/mobile" "[email protected]/laptop")
195```
197### Presence Info Accessors
199```scheme
200(presence-info-jid info) ; => "[email protected]/mobile"
201(presence-info-show info) ; => "available", "away", "dnd", "xa", "chat"
202(presence-info-status info) ; => "Be right back" or #f
203(presence-info-priority info) ; => 0
204```
206## Multi-User Chat (MUC)
208```scheme
209;; Join a room
210(xmpp-muc-join conn "[email protected]" "mynick")
212;; Send a message to the room
213(xmpp-muc-message conn "[email protected]" "Hello room!")
215;; Private message to an occupant
216(xmpp-muc-private-message conn "[email protected]" "alice" "Hi!")
218;; Set room subject
219(xmpp-muc-set-subject conn "[email protected]" "New topic")
221;; Leave the room
222(xmpp-muc-leave conn "[email protected]")
223```
225### Room Administration
227```scheme
228;; Kick an occupant (requires moderator role)
229(xmpp-muc-kick conn "[email protected]" "troublemaker"
230 reason: "Disruptive behavior")
232;; Invite a user
233(xmpp-muc-invite conn "[email protected]" "[email protected]"
234 reason: "Come join us!")
236;; Get current occupants
237(xmpp-muc-occupants conn "[email protected]")
238; => list of muc-occupant records
239```
241### MUC Occupant Accessors
243```scheme
244(muc-occupant-nick occ) ; => "alice"
245(muc-occupant-jid occ) ; => "[email protected]/res" or #f
246(muc-occupant-affiliation occ) ; => "owner", "admin", "member", "none"
247(muc-occupant-role occ) ; => "moderator", "participant", "visitor", "none"
248```
250## JIDs
252```scheme
253;; Parse a JID string
254(define j (parse-jid "[email protected]/mobile"))
255(jid-local j) ; => "user"
256(jid-domain j) ; => "example.com"
257(jid-resource j) ; => "mobile"
259;; Convert back to string
260(jid->string j) ; => "[email protected]/mobile"
262;; Bare JID (without resource)
263(jid-bare "[email protected]/mobile") ; => "[email protected]"
264(jid-bare j) ; => "[email protected]"
265```
267## Stanza Constructors
269All stanzas are represented as SXML and auto-generate unique IDs:
271```scheme
272(xmpp-message to: "[email protected]" body: "Hi" type: "chat")
273; => (message (@ (to "[email protected]") (type "chat") (id "s1-...")) (body "Hi"))
275(xmpp-presence show: "away" status: "BRB")
276; => (presence (@ (id "s2-...")) (show "away") (status "BRB"))
278(xmpp-iq type: "get" to: "example.com"
279 children: (list '(query (@ (xmlns "jabber:iq:roster")))))
280; => (iq (@ (to "example.com") (type "get") (id "s3-...")) (query ...))
281```
283## Stanza Inspection
285```scheme
286(stanza-type stanza) ; => message, presence, iq (symbol)
287(stanza-to stanza) ; => "[email protected]"
288(stanza-from stanza) ; => "[email protected]/res"
289(stanza-id stanza) ; => "s1-abc123"
290(stanza-attr stanza 'type) ; => "chat"
291(message-body stanza) ; => "Hello" or #f
292(stanza-child stanza 'body) ; => (body "Hello") or #f
293(stanza-children stanza 'item) ; => list of matching child elements
294```
296## Common Patterns
298### Echo Bot
300```scheme
301(import (sigil xmpp))
303(define conn (make-xmpp-connection
304 server: "example.com"
306 password: "secret"))
308(xmpp-install-features conn)
310(xmpp-on conn 'message
311 (lambda (stanza)
312 (let ((body (message-body stanza))
313 (from (stanza-from stanza)))
314 (when (and body from)
315 (xmpp-send conn
316 (xmpp-message to: from body: (string-append "Echo: " body)))))))
318(xmpp-connect conn)
319(xmpp-send-presence conn)
320(xmpp-run conn)
321```
323### MUC Bot with Channels
325```scheme
326(import (sigil xmpp)
327 (sigil async)
328 (sigil channels))
330(define conn (make-xmpp-connection
331 server: "example.com"
333 password: "secret"))
335(xmpp-install-features conn)
336(xmpp-connect conn)
337(xmpp-send-presence conn)
338(xmpp-muc-join conn "[email protected]" "bot")
340(with-async
341 (go (xmpp-run conn))
343 (let ((msgs (xmpp-channel conn 'message)))
344 (for-channel msgs
345 (lambda (stanza)
346 (when (equal? (stanza-attr stanza 'type) "groupchat")
347 (let ((body (message-body stanza)))
348 (when (and body (string-starts-with? body "!ping"))
349 (xmpp-muc-message conn
350 "[email protected]" "pong!"))))))))
351```