(module xmpp scheme
(require (planet lizorkin/sxml:2:1/sxml)) (require (planet lizorkin/ssax:2:0/ssax)) (require mzlib/os) (require scheme/tcp) (require openssl) (require srfi/13)
(provide (all-defined-out))
(define port 5222)
(define ssl-port 5223)
(define (open-connection machine port handler)
(let-values (((in out)
(tcp-connect machine port)))
(handler in out)
(close-output-port out)
(close-input-port in)))
(define (open-ssl-connection machine port handler)
(let-values (((in out)
(ssl-connect machine port 'tls)))
(handler in out)
(close-output-port out)
(close-input-port in)))
(define (read-async in)
(bytes->string/utf-8 (list->bytes (read-async-bytes in))))
(define (read-async-bytes in)
(let ((bstr '()))
(when (sync/timeout 0 in)
(set! bstr (cons (read-byte in) (read-async-bytes in)))) bstr))
(define ssxml srl:sxml->xml-noindent)
(define (xmpp-stream host)
(string-append "<?xml version='1.0'?><stream:stream xmlns:stream='http://etherx.jabber.org/streams' to='" host "' xmlns='jabber:client' >"))
(define (xmpp-auth username password resource)
(ssxml `(iq (@ (type "set") (id "auth"))
(query (@ (xmlns "jabber:iq:auth"))
(username ,username)
(password ,password)
(resource ,resource)))))
(define (xmpp-session host)
(ssxml `(iq (@ (to ,host) (type "set") (id "session"))
(session (@ (xmlns "urn:ietf:params:xml:ns:xmpp-session"))))))
(define (message to body)
(ssxml `(message (@ (to ,to)) (body ,body))))
(define (presence #:from (from "")
#:to (to "")
#:type (type "")
#:show (show "")
#:status (status ""))
(cond ((not (string=? status ""))
(ssxml `(presence (@ (type "probe")) (status ,status))))
((string=? type "") "<presence/>")
(else (ssxml `(presence (@ (type ,type)))))))
(define (iq body
#:from (from "")
#:to (to "")
#:type (type "")
#:id (id ""))
(ssxml `(iq (@ (to ,to) (type ,type) ,body))))
(define ((sxpath-element xpath) stanza)
(let ((node ((sxpath xpath) stanza)))
(if (empty? node) "" (car node))))
(define message-from (sxpath-element "message/@from/text()"))
(define message-to (sxpath-element "message/@to/text()"))
(define message-id (sxpath-element "message/@id/text()"))
(define message-type (sxpath-element "message/@type/text()"))
(define message-body (sxpath-element "message/body/text()"))
(define message-subject (sxpath-element "message/subject/text()"))
(define iq-type (sxpath-element "iq/@type/text()"))
(define iq-id (sxpath-element "iq/@id/text()"))
(define iq-error-type (sxpath-element "iq/error/@type/text()"))
(define iq-error-text (sxpath-element "iq/error/text()"))
(define iq-error (sxpath-element "iq/error"))
(define presence-show (sxpath-element "presence/show/text()"))
(define presence-from (sxpath-element "presence/@from/text()"))
(define presence-status (sxpath-element "presence/status/text()"))
(define session->tls? #f)
(define xmpp-handlers (make-hash))
(define (set-xmpp-handler type fcn)
(dict-set! xmpp-handlers type fcn))
(define (remove-xmpp-handler type fcn)
(dict-remove! xmpp-handlers type fcn))
(define (run-xmpp-handler type sz)
(let ((fcn (dict-ref xmpp-handlers type #f)))
(when fcn (begin
(display (format "attempting to run handler ~a.~%" fcn))
(fcn sz)))))
(define (parse-xmpp-response str)
(when (> (string-length str) 0)
(let ((sz (ssax:xml->sxml (open-input-string (clean str)) '())))
(cond
((equal? '(null) (cadr sz))
(newline))
((equal? 'message (caadr sz))
(run-xmpp-handler 'message sz))
((equal? 'iq (caadr sz))
(run-xmpp-handler 'iq sz))
((equal? 'presence (caadr sz))
(run-xmpp-handler 'presence sz))
(else (run-xmpp-handler 'other sz))))))
(define (print-message sz)
(display (format "a ~a message from ~a which says '~a.'~%" (message-type sz) (message-from sz) (message-body sz))))
(define (print-iq sz)
(display (format "an iq response of type '~a' with id '~a.'~%" (iq-type sz) (iq-id sz))))
(define (print-presence sz)
(display (format " p-r-e-s-e-n-e-c--> ~a is ~a" (presence-from sz) (presence-status))))
(define (print-stanza sz)
(display (format "? ?? -> ~%~a~%" sz)))
(define (clean str)
(let ((test (substring str 0 3)))
(cond ((string-ci=? test "<me") str)
((string-ci=? test "<iq") str)
((string-ci=? test "<pr") str)
((string-ci=? test "<ur") str)
(else
(display (format "~%recieved: ~a ~%parsed as <null/>~%~%" str))
"<null/>"))))
(define (xmpp-response-handler in)
(thread (lambda ()
(let loop ()
(parse-xmpp-response (read-async in))
(sleep 0.1) (loop)))))
(define (jid-user jid)
(string-take jid (string-index jid #\@)))
(define (jid-host jid)
(let* ((s (string-take-right jid (- (string-length jid) (string-index jid #\@) 1)))
(v (string-index s #\/)))
(if v (string-take s v) s )))
(define (jid-resource jid)
(let ((r (jid-resource-0 jid)))
(if (void? r) (gethostname) r)))
(define (jid-resource-0 jid)
(let ((v (string-index jid #\/)))
(when v (string-take-right jid (- (string-length jid) v 1)))))
(define xmpp-in-port (make-parameter (current-input-port)))
(define xmpp-out-port (make-parameter (current-output-port)))
(define (send str)
(printf "sending iO: ~a ~%~%" str)
(fprintf (xmpp-out-port) "~A~%" str) (flush-output (xmpp-out-port)))
(define-syntax with-xmpp-session
(syntax-rules ()
((_ jid pass form . forms)
(let ((host (jid-host jid))
(user (jid-user jid))
(resource (jid-resource jid)))
(let-values (((in out)
(ssl-connect host ssl-port 'tls)))
(parameterize ((xmpp-in-port in)
(xmpp-out-port out))
(file-stream-buffer-mode out 'line)
(xmpp-response-handler in)
(send (xmpp-stream host))
(send (xmpp-session host))
(send (xmpp-auth user pass resource))
(send (presence))
(send (presence #:status "Available"))
(begin form . forms)
(close-output-port out)
(close-input-port in)))))))
)