#lang syndicate ;; Dreadfully simplified IRC client driver. (provide (struct-out irc-connection) (struct-out irc-presence) (struct-out irc-inbound) (struct-out irc-outbound)) (define-logger syndicate/drivers/irc) (require racket/format) (require racket/set) (require racket/string) (require syndicate/protocol/advertise) (require/activate syndicate/supervise) (require/activate "tcp.rkt") (require/activate "line-reader.rkt") (struct irc-connection (host port nick) #:prefab) ;; ASSERTION (struct irc-presence (conn nick channel) #:prefab) ;; ASSERTION (struct irc-inbound (conn nick target body) #:prefab) ;; MESSAGE (struct irc-outbound (conn target body) #:prefab) ;; MESSAGE (spawn #:name 'irc-connection-factory (during (observe (irc-inbound $C _ _ _)) (assert C)) (during (observe (observe (irc-outbound $C _ _))) (assert C)) (during/spawn (irc-connection $host $port $nick) #:spawn supervise/spawn #:name (list 'irc-connection host port nick) (define C (irc-connection host port nick)) (define LH (tcp-handle (gensym 'irc))) (define RH (tcp-address host port)) (define (irc-send! . pieces) (send! (tcp-channel LH RH (string->bytes/utf-8 (string-append (apply string-append (map ~a pieces)) "\r\n"))))) (on (asserted (advertise (tcp-channel RH LH _))) (irc-send! "NICK "nick) (irc-send! "USER "nick" 0 * :"nick) (react (during (observe (irc-inbound C _ $target _)) (when (string-prefix? target "#") (on-start (irc-send! "JOIN :"target)) (on-stop (irc-send! "PART :"target)))) (on (message (irc-outbound C $target $body)) (irc-send! "PRIVMSG "target" :"body) (send! (irc-inbound C nick target body))))) (stop-when (retracted (advertise (tcp-channel RH LH _)))) (assert (advertise (tcp-channel LH RH _))) (field [names-tgt #f] [names-acc (set)]) (on (message (tcp-channel-line RH LH $line-bytes)) (log-syndicate/drivers/irc-debug "irc got ~v" line-bytes) (match line-bytes [(regexp #px#"^PING(.*)\r$" (list _ line-tail)) (irc-send! "PONG"line-tail)] [(regexp #px#"^:([^!]+)![^ ]* PRIVMSG ([^ ]+) :(.*)\r$" (list _ src tgt body)) (send! (irc-inbound C (bytes->string/utf-8 src) (bytes->string/utf-8 tgt) (bytes->string/utf-8 body)))] [(regexp #px#"^:[^ ]* 353 [^:]+ ([^ ]+) :(.*)\r$" (list _ tgt names)) (names-tgt (bytes->string/utf-8 tgt)) (define new-names (for/set [(n (string-split (bytes->string/utf-8 names)))] (match n [(regexp #px"@(.*)" (list _ n1)) n1] [(regexp #px"\\+(.*)" (list _ n1)) n1] [n1 n1]))) (names-acc (set-union (names-acc) new-names))] [(regexp #px#"^:[^ ]* 366 " (list _)) (retract! (irc-presence C ? (names-tgt))) (for ((n (names-acc))) (assert! (irc-presence C n (names-tgt)))) (assert! (irc-presence C nick (names-tgt))) ;; ^ (*) Here we note our own presence. We don't do it ;; in response to our own JOIN because we want to make ;; sure that our presence is marked *last* to help ;; preserve some semblance of causal ordering... This ;; is pretty hacky! (names-tgt #f) (names-acc (set))] [(regexp #px#"^:([^!]+)![^ ]* PART ([^ ]+)\r$" (list _ src tgt)) (retract! (irc-presence C (bytes->string/utf-8 src) (bytes->string/utf-8 tgt))) (when (equal? (bytes->string/utf-8 src) nick) (retract! (irc-presence C ? (bytes->string/utf-8 tgt))))] [(regexp #px#"^:([^!]+)![^ ]* QUIT :(.*)\r$" (list _ src _quitmsg)) (retract! (irc-presence C (bytes->string/utf-8 src) ?))] [(regexp #px#"^:([^!]+)![^ ]* JOIN ([^ ]+)\r$" (list _ src-bs tgt)) (define src (bytes->string/utf-8 src-bs)) (when (not (equal? src nick)) ;; See above marked (*) (assert! (irc-presence C src (bytes->string/utf-8 tgt))))] [_ (void)]))))