A Simple IRC Bot to handle Frequently Asked Questions
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

120 lines
3.4 KiB

#lang racket
(require (planet mordae/couchdb:1:11))
(require (planet dherman/json:4:0))
(require net/base64)
(require "redis.rkt")
(require "config.rkt")
; Couch Database operations
; object used to connect to the database
(define (database-connection database-name)
(couchdb-db
(couchdb-connect
#:host (database-info "hostname")
#:port (database-info "port")
#:user (database-info "user")
#:password (database-info "pass"))
database-name))
; gets a single uuid
(define (get-uuid)
(couchdb-get (database-connection "_uuids") ""))
; submits a question
(define couchdb-submit
(compose ((curry couchdb-put)
(database-connection "faqbot"))
; build the jsexpr (hasheq)
(lambda (question)
(hasheq '_id
(first question)
'text
(second question)))
; strip all newlines
(lambda (question)
(list (first question)
(strip-newlines
(second question))))))
; gets a question
(define couchdb-retrieve
(compose
(lambda (result)
(hash-ref result 'text))
((curry couchdb-get)
(database-connection "faqbot"))))
; strips all newlines
(define (strip-newlines text)
(regexp-replace* #rx"\n" text ""))
; Redis Database Operations
; make a command
(define make-cmd set)
; gets a specified command ID from redis
(define (get-id cmd)
(match (get cmd)
['nil 'nil]
[result (match (bytes->string/utf-8 result)
['nil 'nil]
[result (match (regexp-match #rx"^[0-9a-z]*$" result)
[#f 'nil]
[_ result])])]))
; make a command alias
(define (make-alias new-cmd old-cmd)
(match (get-id old-cmd)
['nil 'nil]
[_ (make-cmd
new-cmd
(get-id old-cmd))]))
; associates a name with an id in Redis, then puts the question in couchdb
(define (submit-question name content)
(let* ([id (caar (hash-values [get-uuid]))])
(make-cmd name id)
(couchdb-submit (list id content))))
; gets an id associated with a name from Redis, then gets the question from couchdb
(define (get-question name)
(let* ([id (get-id name)])
(match id
['nil "No question found"]
[_ (couchdb-retrieve id)])))
;; Rate Limiting
; if |hC Current| > k, then set hC to Current, and hN to 0, and allow
; if |hC Current| < k and hN == n, then deny
; if |hC Current| < k, and hN < n, then allow, and increment hN by 1
(define (bytes->number x)
(string->number (bytes->string/utf-8 x)))
; Is there a built-in Racket function to convert byte strings to floats?
(define (time-magnitude a b)
(abs (- b (bytes->number a))))
(define (rate-check-helper mag hN n k)
(let ([hN (bytes->number hN)])
(cond
[(and (> k mag) (eq? hN n)) #f]
[(and (< mag k) (< hN n)) (hincrby "counter" "n" "1") #t]
[else (hset "counter" "n" "0")
(hset "counter" "current" (number->string (current-inexact-milliseconds)))
#t])))
(define ((rate-check n k))
(let* ([counter (hvals "counter")]
[current-time (current-inexact-milliseconds)])
(let* ([hC-Current (time-magnitude (second counter) current-time)])
(rate-check-helper hC-Current (first counter) n k))))
; Allows no more than 20 commands every 5 minutes
; resets after 5 minutes have passed from the last time it was reset
(define allowed? (rate-check 20 (* 5 60000)))
;; Admin Stuff
(provide (all-defined-out))