summaryrefslogtreecommitdiff
path: root/irc
diff options
context:
space:
mode:
Diffstat (limited to 'irc')
-rw-r--r--irc/core.rkt19
-rw-r--r--irc/db.rkt26
-rw-r--r--irc/storage.rkt94
-rw-r--r--irc/users.rkt37
4 files changed, 170 insertions, 6 deletions
diff --git a/irc/core.rkt b/irc/core.rkt
index fa5edce..342cb78 100644
--- a/irc/core.rkt
+++ b/irc/core.rkt
@@ -1,12 +1,14 @@
#lang racket/base
(require (for-syntax racket/provide) racket/provide
- racket/string
+ racket/string racket/list
racket/function racket/promise racket/exn
racket/tcp openssl
(only-in srfi/13 string-index string-index-right))
(provide current-connection current-message current-hook-fail-handler
- connect hook-add hook-process
+ connect hook-add hook-process hook-abort
msg send listen
+ connection-storage set-connection-storage!
+ (matching-identifiers-out #rx"^log-irc-" (all-defined-out))
(matching-identifiers-out #rx"^message-" (all-defined-out)))
(define-logger irc)
@@ -18,7 +20,7 @@
(make-parameter (λ (x) (displayln (exn->string x)))))
(struct connection
- (input output hooks output-lock)
+ (input output hooks output-lock storage)
#:mutable)
(struct message
@@ -29,15 +31,20 @@
(define (connect host port #:ssl (ssl #f))
(define-values (i o) ((if ssl ssl-connect tcp-connect) host port))
- (connection i o '() (make-semaphore 1)))
+ (connection i o '() (make-semaphore 1) #f))
(define (hook-add conn fn)
(set-connection-hooks! conn (cons fn (connection-hooks conn))))
+(define hook-abort (string->uninterned-symbol "abort"))
(define (hook-process msg)
+ (define conn (current-connection))
+ ; TODO add synchronisation
(with-handlers ([exn:fail? (current-hook-fail-handler)])
(parameterize ([current-message msg])
- (for-each (λ (h) (h)) (connection-hooks (current-connection))))))
+ (set-connection-hooks! conn
+ (filter-not (λ (h) (eq? (h) hook-abort))
+ (connection-hooks conn))))))
(define (msg-print-raw msg out)
(define (clean s)
@@ -90,7 +97,7 @@
(message tags prefix (string->symbol command) (reverse args) suffix))
-(define (message-params msg) (append (message-args msg) (message-suffix msg)))
+(define (message-params msg) (append (message-args msg) (list (message-suffix msg))))
(define (message-prefix-nick msg)
(let* ([prefix (message-prefix msg)]
diff --git a/irc/db.rkt b/irc/db.rkt
new file mode 100644
index 0000000..5cba0d3
--- /dev/null
+++ b/irc/db.rkt
@@ -0,0 +1,26 @@
+#lang racket/base
+(require db sql)
+(provide (all-defined-out))
+
+; Assumption: Each connection gets its own database
+
+(define current-database (make-parameter #f))
+
+(define (db-init)
+ (query-exec (current-database)
+ (create-table #:if-not-exists users
+ #:columns [name varchar #:not-null]
+ [power integer #:not-null]
+ [data varchar #:not-null]
+ #:constraints (primary-key name)))
+
+ (query-exec (current-database)
+ (create-table #:if-not-exists channels
+ #:columns [name varchar #:not-null]
+ [autojoin boolean #:not-null]
+ [data varchar #:not-null]))
+
+ (query-exec (current-database)
+ (create-table #:if-not-exists network
+ #:columns [name varchar #:not-null]
+ [data varchar #:not-null])))
diff --git a/irc/storage.rkt b/irc/storage.rkt
new file mode 100644
index 0000000..d1425de
--- /dev/null
+++ b/irc/storage.rkt
@@ -0,0 +1,94 @@
+#lang racket/base
+(require irc/core irc/bot irc/db irc/users
+ racket/list racket/function
+ racket/format racket/serialize
+ db sql)
+(provide (all-defined-out))
+
+(define (current-storage) (connection-storage (current-connection)))
+
+(struct storage
+ (user volatile-user
+ channel volatile-channel
+ network volatile-network))
+
+(define (storage-init)
+ (set-connection-storage!
+ (current-connection)
+ (storage (load-storage (select name data #:from users)) (make-hash)
+ (load-storage (select name data #:from channels)) (make-hash)
+ (load-storage (select name data #:from network)) (make-hash))))
+
+; TODO !!! IRC downcasing of nicks
+
+(define (user-storage user #:volatile? [volatile #f])
+ (hash-ref! ((if volatile storage-volatile-user storage-user)
+ (current-storage))
+ user (thunk (make-hash))))
+
+(define (channel-storage channel #:volatile? [volatile #f])
+ (hash-ref! ((if volatile storage-volatile-channel storage-channel)
+ (current-storage)))
+ channel (thunk (make-hash)))
+
+(define (network-storage name #:volatile? [volatile #f])
+ (hash-ref! ((if volatile storage-volatile-network storage-network)
+ (current-storage))
+ name (thunk (make-hash))))
+
+(define (load-storage query)
+ (define storage (make-hash))
+ (for ([(name data) (in-query (current-database) query)])
+ (hash-set! storage name (deserialize (safe-read-string data))))
+ storage)
+
+(define empty-serialized-hash
+ (~s (serialize (make-hash))))
+
+(define (persist-user user)
+ (query-exec (current-database)
+ (insert #:into users
+ #:set [name ,user]
+ [power 0]
+ [data ,empty-serialized-hash]
+ #:or-ignore))
+
+ (query-exec (current-database)
+ (update users
+ #:set [data ,(~s (serialize (user-storage user)))]
+ #:where (= name ,user))))
+
+(define (persist-channel channel)
+ (query-exec (current-database)
+ (insert #:into channels
+ #:set [name ,channel]
+ [autojoin 0]
+ [data ,empty-serialized-hash]
+ #:or-ignore))
+
+ (query-exec (current-database)
+ (update channels
+ #:set [data ,(~s (serialize (channel-storage channel)))]
+ #:where (= name ,channel))))
+
+(define (persist-network name)
+ (query-exec (current-database)
+ (insert #:into network
+ #:set [name ,name]
+ [data ,empty-serialized-hash]
+ #:or-ignore))
+
+ (query-exec (current-database)
+ (update network
+ #:set [data ,(~s (serialize (network-storage name)))]
+ #:where (= name ,name))))
+
+(define (ref h . path)
+ (foldl (λ (p h) (hash-ref h p)) h path))
+
+(define (ref-set! h value . path)
+ (if (pair? (cdr path))
+ (apply ref-set!
+ (hash-ref! h (car path) (thunk (make-hash)))
+ value (cdr path))
+ (hash-set! h (car path) value)))
diff --git a/irc/users.rkt b/irc/users.rkt
new file mode 100644
index 0000000..330b965
--- /dev/null
+++ b/irc/users.rkt
@@ -0,0 +1,37 @@
+#lang racket/base
+(require irc/core irc/bot irc/db
+ racket/match racket/string
+ db sql)
+(provide (all-defined-out))
+
+; Power levels
+; 0 untrusted
+; 1 trusted
+; 2 admin
+
+(define (user-power user)
+ (or (query-maybe-value (current-database)
+ (select power #:from users #:where (= name ,user)))
+ 0))
+
+(define (is-untrusted? user) (= 0 (user-power user)))
+(define (is-trusted? user) (= 1 (user-power user)))
+(define (is-admin? user) (= 2 (user-power user)))
+
+; Do VERSION, wait for reply
+; Store CASEMAPPING in network storage
+; Implement case mapping, dependent on that value, with fallback
+
+; TODO This can leak on repeated timeouts
+(define (is-identified? user)
+ (send (msg 'WHO '() user))
+ (define ch (make-channel))
+ (on (command-is '|352|)
+ (match-define
+ (list-rest own-nick channel _user host
+ server nick mode suffix) (params))
+ (when (string=? nick user)
+ (channel-put ch (string-contains? mode "r"))
+ hook-abort))
+ (sync/timeout 15 ch))
+