sigildocs

XMPP

XMPP client library with STARTTLS, SASL authentication, roster, presence, and MUC support.
(import (sigil xmpp))

Connecting

(define conn (make-xmpp-connection
               server: "example.com"
               jid: "bot@example.com"
               password: "secret"
               resource: "bot"))

;; Install roster, presence, and MUC tracking
(xmpp-install-features conn)

;; Connect (TCP -> STARTTLS -> SASL -> resource bind)
(xmpp-connect conn)

;; Announce availability
(xmpp-send-presence conn)

;; Run the event loop (blocks until disconnected)
(xmpp-run conn)

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.

xmpp-run processes incoming stanzas. Inside a with-async context it cooperates with other tasks via await-readable; otherwise it blocks with socket-select.

Sending Messages

;; Chat message
(xmpp-send conn
  (xmpp-message to: "friend@example.com" body: "Hello!"))

;; Message with subject
(xmpp-send conn
  (xmpp-message to: "friend@example.com"
                body: "Check this out"
                subject: "Interesting"))

Handling Events (Callbacks)

Register handlers for specific event types with xmpp-on:

(xmpp-on conn 'message
  (lambda (stanza)
    (let ((body (message-body stanza))
          (from (stanza-from stanza)))
      (when (and body from)
        (display (string-append from ": " body "\n"))))))

(xmpp-on conn 'presence
  (lambda (stanza)
    (display (string-append (stanza-from stanza) " is now "
                            (or (stanza-attr stanza 'type) "available") "\n"))))

(xmpp-on conn 'connected
  (lambda (conn)
    (display "Connected!\n")))

(xmpp-on conn 'disconnected
  (lambda (conn)
    (display "Disconnected.\n")))

Event types: message, presence, iq, stanza, connected, disconnected, error.

xmpp-on-stanza registers a handler for all incoming stanzas regardless of type.

Handling Events (Channels)

For sequential, composable event processing use xmpp-channel:

(import (sigil xmpp)
        (sigil async)
        (sigil channels))

(with-async
  (go (xmpp-run conn))

  (let ((msgs (xmpp-channel conn 'message)))
    (for-channel msgs
      (lambda (stanza)
        (let ((body (message-body stanza)))
          (when body
            (xmpp-send conn
              (xmpp-message to: (stanza-from stanza)
                            body: (string-append "Echo: " body)))))))))

Channels work with channel-receive, for-channel, and channel-select for multiplexing:

(with-async
  (go (xmpp-run conn))

  (let ((msgs (xmpp-channel conn 'message))
        (pres (xmpp-channel conn 'presence)))
    (let loop ()
      (channel-select
        (msgs => (lambda (s) (handle-message s)))
        (pres => (lambda (s) (handle-presence s))))
      (loop))))

IQ Requests

Send IQ stanzas with automatic response correlation:

(xmpp-send-iq conn
  (xmpp-iq type: "get"
           to: "example.com"
           children: (list '(query (@ (xmlns "http://jabber.org/protocol/disco#info")))))
  (lambda (response)
    (display "Got disco response\n")))

The callback is invoked once when the server replies with a matching IQ result or error.

Roster

;; Fetch the roster
(xmpp-request-roster conn
  (lambda (items)
    (for-each (lambda (item)
                (display (string-append (roster-item-jid item) "\n")))
              items)))

;; Access cached roster (after fetch)
(xmpp-roster conn)          ; => list of roster-item, or #f
(xmpp-roster-item conn "alice@example.com")  ; => roster-item or #f

;; Add a contact
(xmpp-roster-add conn "friend@example.com" name: "Friend")

;; Remove a contact
(xmpp-roster-remove conn "old@example.com")

Roster Item Accessors

(roster-item-jid item)           ; => "alice@example.com"
(roster-item-name item)          ; => "Alice" or #f
(roster-item-subscription item)  ; => "both", "from", "to", "none"
(roster-item-groups item)        ; => ("Friends" "Work")

Subscriptions

(xmpp-subscribe conn "user@example.com")
(xmpp-accept-subscription conn "user@example.com")
(xmpp-deny-subscription conn "user@example.com")
(xmpp-unsubscribe conn "user@example.com")

Presence

;; Send available presence
(xmpp-send-presence conn)

;; Set status
(xmpp-set-status conn show: "away" status: "Be right back")

;; Go offline and disconnect
(xmpp-go-offline conn)

Querying Presence

With xmpp-install-features, presence is tracked automatically:

;; Get presence for a full JID
(xmpp-presence-of conn "alice@example.com/mobile")
; => #<presence-info jid: "..." show: "available" status: #f>

;; Get all online resources for a bare JID
(xmpp-resources-of conn "alice@example.com")
; => ("alice@example.com/mobile" "alice@example.com/laptop")

Presence Info Accessors

(presence-info-jid info)       ; => "alice@example.com/mobile"
(presence-info-show info)      ; => "available", "away", "dnd", "xa", "chat"
(presence-info-status info)    ; => "Be right back" or #f
(presence-info-priority info)  ; => 0

Multi-User Chat (MUC)

;; Join a room
(xmpp-muc-join conn "room@conference.example.com" "mynick")

;; Send a message to the room
(xmpp-muc-message conn "room@conference.example.com" "Hello room!")

;; Private message to an occupant
(xmpp-muc-private-message conn "room@conference.example.com" "alice" "Hi!")

;; Set room subject
(xmpp-muc-set-subject conn "room@conference.example.com" "New topic")

;; Leave the room
(xmpp-muc-leave conn "room@conference.example.com")

Room Administration

;; Kick an occupant (requires moderator role)
(xmpp-muc-kick conn "room@conference.example.com" "troublemaker"
               reason: "Disruptive behavior")

;; Invite a user
(xmpp-muc-invite conn "room@conference.example.com" "friend@example.com"
                 reason: "Come join us!")

;; Get current occupants
(xmpp-muc-occupants conn "room@conference.example.com")
; => list of muc-occupant records

MUC Occupant Accessors

(muc-occupant-nick occ)         ; => "alice"
(muc-occupant-jid occ)          ; => "alice@example.com/res" or #f
(muc-occupant-affiliation occ)  ; => "owner", "admin", "member", "none"
(muc-occupant-role occ)         ; => "moderator", "participant", "visitor", "none"

JIDs

;; Parse a JID string
(define j (parse-jid "user@example.com/mobile"))
(jid-local j)     ; => "user"
(jid-domain j)    ; => "example.com"
(jid-resource j)  ; => "mobile"

;; Convert back to string
(jid->string j)   ; => "user@example.com/mobile"

;; Bare JID (without resource)
(jid-bare "user@example.com/mobile")  ; => "user@example.com"
(jid-bare j)                          ; => "user@example.com"

Stanza Constructors

All stanzas are represented as SXML and auto-generate unique IDs:

(xmpp-message to: "user@ex.com" body: "Hi" type: "chat")
; => (message (@ (to "user@ex.com") (type "chat") (id "s1-...")) (body "Hi"))

(xmpp-presence show: "away" status: "BRB")
; => (presence (@ (id "s2-...")) (show "away") (status "BRB"))

(xmpp-iq type: "get" to: "example.com"
         children: (list '(query (@ (xmlns "jabber:iq:roster")))))
; => (iq (@ (to "example.com") (type "get") (id "s3-...")) (query ...))

Stanza Inspection

(stanza-type stanza)   ; => message, presence, iq (symbol)
(stanza-to stanza)     ; => "user@example.com"
(stanza-from stanza)   ; => "other@example.com/res"
(stanza-id stanza)     ; => "s1-abc123"
(stanza-attr stanza 'type)  ; => "chat"
(message-body stanza)  ; => "Hello" or #f
(stanza-child stanza 'body)      ; => (body "Hello") or #f
(stanza-children stanza 'item)   ; => list of matching child elements

Common Patterns

Echo Bot

(import (sigil xmpp))

(define conn (make-xmpp-connection
               server: "example.com"
               jid: "bot@example.com"
               password: "secret"))

(xmpp-install-features conn)

(xmpp-on conn 'message
  (lambda (stanza)
    (let ((body (message-body stanza))
          (from (stanza-from stanza)))
      (when (and body from)
        (xmpp-send conn
          (xmpp-message to: from body: (string-append "Echo: " body)))))))

(xmpp-connect conn)
(xmpp-send-presence conn)
(xmpp-run conn)

MUC Bot with Channels

(import (sigil xmpp)
        (sigil async)
        (sigil channels))

(define conn (make-xmpp-connection
               server: "example.com"
               jid: "bot@example.com"
               password: "secret"))

(xmpp-install-features conn)
(xmpp-connect conn)
(xmpp-send-presence conn)
(xmpp-muc-join conn "room@conference.example.com" "bot")

(with-async
  (go (xmpp-run conn))

  (let ((msgs (xmpp-channel conn 'message)))
    (for-channel msgs
      (lambda (stanza)
        (when (equal? (stanza-attr stanza 'type) "groupchat")
          (let ((body (message-body stanza)))
            (when (and body (string-starts-with? body "!ping"))
              (xmpp-muc-message conn
                "room@conference.example.com" "pong!"))))))))