215 lines
9.0 KiB
Plaintext
215 lines
9.0 KiB
Plaintext
#!/usr/bin/env newlisp
|
|
|
|
;; @module IRC
|
|
;; @description a basic irc library
|
|
;; @version early alpha! 0.1 2011-10-31 14:21:26
|
|
;; @author cormullion
|
|
;; Usage:
|
|
;; (IRC:init "newlithper") ; a username/nick (not that one obviously :-)
|
|
;; (IRC:connect "irc.freenode.net" 6667) ; irc/server
|
|
;; (IRC:join-channel {#newlisp}) ; join a room
|
|
;; either (IRC:read-irc-loop) ; loop - monitor only, no input
|
|
;; or (IRC:session) ; a command-line session, end with /QUIT
|
|
|
|
(context 'IRC)
|
|
(define Inickname)
|
|
(define Ichannels)
|
|
(define Iserver)
|
|
(define Iconnected)
|
|
(define Icallbacks '())
|
|
(define Idle-time 400) ; seconds
|
|
(define Itime-stamp) ; time since last message was processed
|
|
|
|
(define (register-callback callback-name callback-function)
|
|
(println {registering callback for } callback-name { : } (sym (term callback-function) (prefix callback-function)))
|
|
(push (list callback-name (sym (term callback-function) (prefix callback-function))) Icallbacks))
|
|
|
|
(define (do-callback callback-name data)
|
|
(when (set 'func (lookup callback-name Icallbacks)) ; find first callback
|
|
(if-not (catch (apply func (list data)) 'error)
|
|
(println {error in callback } callback-name {: } error))))
|
|
|
|
(define (do-callbacks callback-name data)
|
|
(dolist (rf (ref-all callback-name Icallbacks))
|
|
(set 'callback-entry (Icallbacks (first rf)))
|
|
(when (set 'func (last callback-entry))
|
|
(if-not (catch (apply func (list data)) 'error)
|
|
(println {error in callback } callback-name {: } error)))))
|
|
|
|
(define (init str)
|
|
(set 'Inickname str)
|
|
(set 'Iconnected nil)
|
|
(set 'Ichannels '())
|
|
(set 'Itime-stamp (time-of-day)))
|
|
|
|
(define (connect server port)
|
|
(set 'Iserver (net-connect server port))
|
|
(net-send Iserver (format "USER %s %s %s :%s\r\n" Inickname Inickname Inickname Inickname))
|
|
(net-send Iserver (format "NICK %s \r\n" Inickname))
|
|
(set 'Iconnected true)
|
|
(do-callbacks "connect" (list (list "server" server) (list "port" port))))
|
|
|
|
(define (identify password)
|
|
(net-send Iserver (format "PRIVMSG nickserv :identify %s\r\n" password)))
|
|
|
|
(define (join-channel channel)
|
|
(when (net-send Iserver (format "JOIN %s \r\n" channel))
|
|
(push channel Ichannels)
|
|
(do-callbacks "join-channel" (list (list "channel" channel) (list "nickname" Inickname)))))
|
|
|
|
(define (part chan)
|
|
(if-not (empty? chan)
|
|
; leave specified
|
|
(begin
|
|
(net-send Iserver (format "PART %s\r\n" chan))
|
|
(replace channel Ichannels)
|
|
(do-callbacks "part" (list (list "channel" channel))))
|
|
; leave all
|
|
(begin
|
|
(dolist (channel Ichannels)
|
|
(net-send Iserver (format "PART %s\r\n" channel))
|
|
(replace channel Ichannels)
|
|
(do-callbacks "part" (list (list "channel" channel)))))))
|
|
|
|
(define (do-quit message)
|
|
(do-callbacks "quit" '()) ; chance to do stuff before quit...
|
|
(net-send Iserver (format "QUIT :%s\r\n" message))
|
|
(sleep 1000)
|
|
(set 'Ichannels '())
|
|
(close Iserver)
|
|
(set 'Iconnected nil))
|
|
|
|
(define (privmsg user message)
|
|
(net-send Iserver (format "PRIVMSG %s :%s\r\n" user message)))
|
|
|
|
(define (notice user message)
|
|
(net-send Iserver (format "NOTICE %s :%s\r\n" user message)))
|
|
|
|
(define (send-to-server message (channel nil))
|
|
(cond
|
|
((starts-with message {/}) ; default command character
|
|
(set 'the-message (replace "^/" (copy message) {} 0)) ; keep original
|
|
(net-send Iserver (format "%s \r\n" the-message)) ; send it
|
|
; do a quit
|
|
(if (starts-with (lower-case the-message) "quit")
|
|
(do-quit { enough})))
|
|
(true
|
|
(if (nil? channel)
|
|
; say to all channels
|
|
(dolist (c Ichannels)
|
|
(net-send Iserver (format "PRIVMSG %s :%s\r\n" c message)))
|
|
; say to specified channel
|
|
(if (find channel Ichannels)
|
|
(net-send Iserver (format "PRIVMSG %s :%s\r\n" channel message))))))
|
|
(do-callbacks "send-to-server" (list (list "channel" channel) (list "message" message))))
|
|
|
|
(define (process-command sender command text)
|
|
(cond
|
|
((= sender "PING")
|
|
(net-send Iserver (format "PONG %s\r\n" command)))
|
|
((or (= command "NOTICE") (= command "PRIVMSG"))
|
|
(process-message sender command text))
|
|
((= command "JOIN")
|
|
(set 'username (first (clean empty? (parse sender {!|:} 0))))
|
|
(set 'channel (last (clean empty? (parse sender {!|:} 0))))
|
|
(println {username } username { joined } channel)
|
|
(do-callbacks "join" (list (list "channel" channel) (list "username" username))))
|
|
(true
|
|
nil)))
|
|
|
|
(define (process-message sender command text)
|
|
(let ((username {} target {} message {}))
|
|
(set 'username (first (clean empty? (parse sender {!|:} 0))))
|
|
(set 'target (trim (first (clean empty? (parse text {!|:} 0)))))
|
|
(set 'message (slice text (+ (find {:} text) 1)))
|
|
(cond
|
|
((starts-with message "\001")
|
|
(process-ctcp username target message))
|
|
((find target Ichannels)
|
|
(cond
|
|
((= command {PRIVMSG})
|
|
(do-callbacks "channel-message" (list (list "channel" target) (list "username" username) (list "message" message))))
|
|
((= command {NOTICE})
|
|
(do-callbacks "channel-notice" (list (list "channel" target) (list "username" username) (list "message" message))))))
|
|
((= target Inickname)
|
|
(cond
|
|
((= command {PRIVMSG})
|
|
(do-callbacks "private-message" (list (list "username" username) (list "message" message))))
|
|
((= command {NOTICE})
|
|
(do-callbacks "private-notice" (list (list "username" username) (list "message" message))))))
|
|
(true
|
|
nil))))
|
|
|
|
(define (process-ctcp username target message)
|
|
(cond
|
|
((starts-with message "\001VERSION\001")
|
|
(net-send Iserver (format "NOTICE %s :\001VERSION %s\001\r\n" username version)))
|
|
((starts-with message "\001PING")
|
|
(set 'data (first (rest (clean empty? (parse message { } 0)))))
|
|
(set 'data (trim data "\001" "\001"))
|
|
(net-send Iserver (format "NOTICE %s :\001PING %s\001\r\n" username data)))
|
|
((starts-with message "\001ACTION")
|
|
(set 'data (first (rest (clean empty? (parse message { } 0)))))
|
|
(set 'data (join data { }))
|
|
(set 'data (trim data "\001" "\001"))
|
|
(if (find target Ichannels)
|
|
(do-callbacks "channel-action" (list (list "username" username) (list "message" message))))
|
|
(if (= target Inickname)
|
|
(do-callbacks "private-action" (list (list "username" username) (list "message" message)))))
|
|
((starts-with message "\001TIME\001")
|
|
(net-send Iserver (format "NOTICE %s:\001TIME :%s\001\r\n" username (date))))))
|
|
|
|
(define (parse-buffer raw-buffer)
|
|
(let ((messages (clean empty? (parse raw-buffer "\r\n" 0)))
|
|
(sender {} command {} text {}))
|
|
; check for elapsed time since last activity
|
|
(when (> (sub (time-of-day) Itime-stamp) (mul Idle-time 1000))
|
|
(do-callbacks "idle-event")
|
|
(set 'Itime-stamp (time-of-day)))
|
|
(dolist (message messages)
|
|
(set 'message-parts (parse message { }))
|
|
(unless (empty? message-parts)
|
|
(set 'sender (first message-parts))
|
|
(catch (set 'command (first (rest message-parts))) 'error)
|
|
(catch (set 'text (join (rest (rest message-parts)) { })) 'error))
|
|
(process-command sender command text))))
|
|
|
|
(define (read-irc)
|
|
(let ((buffer {}))
|
|
(when (!= (net-peek Iserver) 0)
|
|
(net-receive Iserver buffer 8192 "\n")
|
|
(unless (empty? buffer)
|
|
(parse-buffer buffer)))))
|
|
|
|
(define (read-irc-loop) ; monitoring
|
|
(let ((buffer {}))
|
|
(while Iconnected
|
|
(read-irc)
|
|
(sleep 1000))))
|
|
|
|
(define (print-raw-message data) ; example of using a callback
|
|
(set 'raw-data (lookup "message" data))
|
|
(set 'channel (lookup "channel" data))
|
|
(set 'message-text raw-data)
|
|
(println (date (date-value) 0 {%H:%M:%S }) username {> } message-text))
|
|
|
|
(define (print-outgoing-message data)
|
|
(set 'raw-data (lookup "message" data))
|
|
(set 'channel (lookup "channel" data))
|
|
(set 'message-text raw-data)
|
|
(println (date (date-value) 0 {%H:%M:%S }) Inickname {> } message-text))
|
|
|
|
(define (session); interactive terminal
|
|
; must add callbacks to display messages
|
|
(register-callback "channel-message" 'print-raw-message)
|
|
(register-callback "send-to-server" 'print-outgoing-message)
|
|
(while Iconnected
|
|
(while (zero? (peek 0))
|
|
(read-irc))
|
|
(send-to-server (string (read-line 0))))
|
|
(println {finished session } (date))
|
|
(exit))
|
|
|
|
; end of IRC code
|
|
|