Created
December 16, 2012 22:16
-
-
Save jaen/4313655 to your computer and use it in GitHub Desktop.
Crude Clojure LISP bot using Aleph.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns jaen-bot.core | |
(:use [lamina.core] | |
[aleph.tcp] | |
[gloss.core] | |
[clojure.java.io]) | |
(:require [net.cgrand.regex :as r] | |
[clojure.string :as s] | |
[cheshire.core :as json])) | |
(def config (atom {})) | |
(def state (atom :connected)) | |
(def auth-lines (atom 0)) | |
(def users-online (atom #{})) | |
(def pluspluses (atom {})) | |
(def parrot-talkbacks (atom #{})) | |
(def siema-count (atom 0)) | |
(def global-irc-connection (atom nil)) | |
; GOD WHERE ARE MY NAMED CAPTURES | |
; positionally [0] = (optional) from; [1] = (mandatory) command; [2] = (optional) body | |
(def line-regex #"^(?::([^\s]+))?([^:]+)(?::(.*))?$") | |
; [0] = nick [1] = realname | |
(def nick-regex #"^([^!]+)!~?([^@]+)@.+$") | |
(def directed-at-regex #"^\s*([^:]+)(?:(?::|\s+)(.+))?$") | |
(def point-regex #"\s*([^\+\s]+)(\+\+|--)s*") | |
(def kick-talkbacks (atom [])) | |
(def talkbacks (atom [])) | |
(defn ask-reply | |
[] | |
(read-line)) | |
(defn directed-at | |
[body] | |
(if body | |
(let [[_ nick message] (re-matches directed-at-regex body)] | |
(if message nick)) | |
nil)) | |
(defn extract-directed-body | |
[body] | |
(let [[_ nick message] (re-matches directed-at-regex body)] | |
message)) | |
(defn msg | |
[where & lines] | |
(vec (map #(format "PRIVMSG %s :%s" where %) lines))) | |
(defn merge-response-map | |
[& maps] | |
(let [new-replies (apply merge (map #(% :replies) maps))] | |
(apply merge (flatten [maps {:replies new-replies}])))) | |
(defn protocol-reply | |
[line] | |
(do | |
(let [match (re-matches line-regex line) | |
from (match 1) | |
command-match (s/split (s/trim (match 2)) #"\s+") | |
command (first command-match) | |
args (vec (rest command-match)) | |
body (match 3) | |
[_ nick realname] (and from (re-matches nick-regex from)) | |
reply (if (= nick (@config :bot-nick)) ; is this is an acknowledgement? | |
(cond | |
(and (= command "MODE") | |
(let [[nick mode] args] | |
(and (= nick (@config :bot-nick)) | |
(= mode (@config :bot-mode))))) (do | |
(reset! state :joined) | |
(vec (flatten [(map #(format "JOIN %s" %) (@config :bot-join-channels))])))) | |
(cond | |
(= command "PING") (format "PONG :%s" body) | |
(= command "KICK") (if (= (args 1) (@config :bot-nick)) (do (Thread/sleep (+ 3200 (rand-int 2800))) (vec (flatten [(format "JOIN %s" (args 0)) (msg (args 0) (format "%s: %s" nick (rand-nth @kick-talkbacks)))])) [])) | |
(= command "PART") (do (swap! users-online disj nick) []) | |
(= command "JOIN") (do (swap! users-online conj nick) []) | |
(= command "NICK") (do (swap! users-online disj nick) (swap! users-online conj (s/trim body)) []) | |
(= command "001") (do (reset! state :authed) []) | |
(= command "353") (do (doseq [nick (map #((re-matches #"@?([^@]+)" %) 1) (s/split body #"\s+"))] (swap! users-online conj nick)) []) | |
(and (>= @auth-lines 3) | |
(= @state :connected)) [(format "NICK %s" (@config :bot-nick)) (format "USER %s %s a b" (@config :bot-realname) (@config :bot-mode))] | |
(= command "NOTICE") (cond | |
(= (args 0) "AUTH") (do (println "auth line") (swap! auth-lines inc) []))))] | |
(if (not (nil? reply)) | |
{:line-data { :from [nick realname] | |
:command command | |
:args args | |
:body body } | |
:replies { :protocol-reply (cond (vector? reply) reply | |
true [reply])}} | |
{:line-data { :from [nick realname] | |
:command command | |
:args args | |
:body body }} )))) | |
;OH GOD GLOBAL STATE | |
(defn rejoin | |
[channel] | |
(enqueue @global-irc-connection (format "JOIN %s" channel))) | |
(defn talkback | |
[response-map] | |
(let [line-data (or (response-map :line-data) {}) | |
command (line-data :command) | |
args (line-data :args) | |
body (line-data :body) | |
[nick realname] (line-data :from) | |
can-talkback (not (response-map :inhibit-talkback)) | |
reply (if can-talkback | |
(if (and (= command "PRIVMSG") (some #{(args 0)} (@config :bot-join-channels))) | |
(let [from-channel (args 0)] | |
(cond | |
(= (directed-at body) (@config :bot-nick)) (cond | |
(or (= nick "firemark") (= realname "tiramo")) (msg from-channel "firemark: siema!") | |
(re-find #"przywitaj\s*si(e|ę)" body) (msg from-channel "no witam") | |
(re-find #"siema!?" body) (if (> @siema-count (+ 2 (rand-int 3))) | |
(do | |
(.start (Thread. (fn [] (Thread/sleep (+ 30000 (rand-int 30000))) | |
(rejoin from-channel)))) | |
(reset! siema-count 0) | |
(Thread/sleep (+ 2200 (rand-int 1800))) | |
(vec (flatten [(msg from-channel "a pierdolcie sie wszyscy!") (format "PART %s" from-channel)] ))) | |
(do | |
(Thread/sleep (+ 1800 (rand-int 1200))) | |
(swap! siema-count inc) | |
(msg from-channel (rand-nth ["spierdalaj" "pierdol sie" "zamknie morde"])))) | |
true (msg from-channel (format "%s: %s" nick (rand-nth @talkbacks))))))))] | |
(if reply | |
(merge-response-map response-map {:replies {:talkback-reply reply }}) | |
response-map))) | |
(defn parrot | |
[response-map] | |
(let [line-data (or (response-map :line-data) {}) | |
body (line-data :body) | |
plain-body (if (directed-at body) (extract-directed-body body) body) | |
shall-store (<= (rand) (@config :parrot-register-probability)) | |
shall-reply (and (<= (rand) (@config :parrot-reply-probability)) (not shall-store)) | |
reply (if (and (= (line-data :command) "PRIVMSG") (some #{((line-data :args) 0)} (@config :bot-join-channels))) | |
(cond | |
shall-store (do (swap! parrot-talkbacks conj plain-body) (println "Parrot talkback registered:" plain-body) nil) | |
shall-reply (msg ((line-data :args) 0) (rand-nth @parrot-talkbacks))))] | |
(if reply | |
(do | |
(Thread/sleep (+ 1200 (rand-int 1800))) | |
(merge-response-map (assoc response-map :inhibit-talkback true) { :replies { :parrot-reply reply } })) | |
response-map))) | |
(defn word-join | |
[& words] | |
(let | |
[commas (drop-last words) | |
last-word (last words)] | |
(cond | |
(empty? commas) last-word | |
(= (count commas) 4) (format "%s i %s" (s/join ", " commas) last-word) | |
true (format "%s i ktośtam tam pewnie jeszcze" (s/join ", " words))))) | |
(defn count-pluspluses | |
[response-map] | |
(let [line-data (or (response-map :line-data) {}) | |
command (line-data :command) | |
args (line-data :args) | |
from-channel (if (= command "PRIVMSG") (args 0))] | |
(if (and (= command "PRIVMSG") (some #{from-channel} (@config :bot-join-channels))) | |
(if (let [body (and line-data (line-data :body))] | |
(and body | |
(and (= (directed-at body) (@config :bot-nick)) (not (nil? (re-find #"(stan plus(plus)?(ó|o)w|(plus)?plusy)" body)))))) | |
(merge-response-map response-map { :replies { :count-pluspluses-reply (apply msg from-channel | |
[(s/join ", " (map (fn [k v] (format "%s: %s" k v)) | |
(keys @pluspluses) (vals @pluspluses)))]) } | |
:inhibit-talkback true}) | |
(let [line-data (or (response-map :line-data) {}) | |
body (line-data :body) | |
[_ nick] (line-data :from) | |
plusplus-matches (or (and body (re-seq point-regex body)) []) | |
nested-responses (map (fn [[_ plussed-nick what]] (if (and (contains? @users-online plussed-nick) (not (= plussed-nick nick))) | |
(do | |
(let | |
[op (cond (= what "++") inc (= what "--") dec)] | |
(swap! pluspluses (fn [{value plussed-nick :as pluspluses}] (assoc pluspluses plussed-nick (op (or value 0)))))) | |
[]) | |
(if (= plussed-nick nick) [plussed-nick] []))) ; (if (= plussed-nick nick) :selfplus)])) | |
plusplus-matches) | |
responses (if (empty? nested-responses) [] (reduce into nested-responses))] | |
(do | |
(if (not (empty? responses)) | |
(let [self-plusses (filter (fn [[_ sp]] (not (nil? sp))) responses) | |
invalid-plusses (filter (fn [[_ sp]] (nil? sp)) responses) | |
self-plus-reply (if (not (empty? self-plusses)) (msg from-channel (format "%s: tylko Widzew się samoplusuje. Chcesz być jak Widzew?" nick))) | |
invalid-reply (msg from-channel (format "%s: ja, mhm, i jeszcze mi powiesz że %s %s? " nick (apply word-join responses) (if (= 1 (count responses)) "istnieje" "wszyscy istnieją")))] | |
response-map);(merge-response-map response-map { :replies { :count-pluspluses-reply invalid-reply } })) | |
response-map)))) | |
response-map))) | |
(defn foo | |
[] | |
(let [irc-frame (string :utf-8 :delimiters ["\r\n"]) | |
irc-connection @(tcp-client {:host (@config :server) :port (@config :port) :frame irc-frame}) | |
irc-pipeline (fn [line] (run-pipeline { } | |
(fn [response-map] | |
(do | |
(println "IRC SAYS:" line) | |
(assoc response-map :line line))) | |
(fn [response-map] | |
(merge-response-map response-map | |
(protocol-reply line))) | |
(fn [response-map] | |
(count-pluspluses response-map)) | |
(fn [response-map] | |
(parrot response-map)) | |
(fn [response-map] | |
(talkback response-map)) | |
(fn [{:keys [replies] :as response-map}] | |
(do | |
(doseq [[from reply] replies] | |
(doseq [line reply] | |
(println (format "I REPLY (%s): %s" from line)) | |
(enqueue irc-connection line)))))))] | |
(reset! global-irc-connection irc-connection) | |
(receive-all irc-connection irc-pipeline))) | |
(def default-config { | |
:server "irc.quakenet.org" | |
:port 6667 | |
:bot-nick "failmarg" | |
:bot-realname "yuuki-bot" | |
:bot-mode "+iw" | |
:bot-join-channels ["#lobos"] ;["#inf.aei.polsl.pl" "#lobos"] | |
:kick-talkbacks ["wstydziłbyś się waćpan!" "KARRRRAMBA" "terefere mi stąd!" "idź mścij się na firemarku!" "how about a nice warm cup of fuck the hell off?"] | |
:talkbacks ["no siema" "hej" "witaj!" "i ty też terefere!" "; )" "miło mi" "why, hello there!"] | |
}) | |
(defn -main | |
"I don't do a whole lot." | |
[& args] | |
(let | |
[cwd (System/getProperty "user.dir") | |
merged-config (merge default-config (json/parse-stream (clojure.java.io/reader (format "%s/data/config.json" cwd)) #(keyword %))) | |
stored-pluspluses (json/parse-stream (clojure.java.io/reader (format "%s/data/pluspluses.json" cwd))) | |
stored-parrot-lines (json/parse-stream (clojure.java.io/reader (format "%s/data/parrot.json" cwd)))] | |
(do | |
(println "Cwd:" cwd) | |
(reset! config merged-config) | |
(println "Hello, World!" (@config :bot-nick)) | |
(println "Config:") | |
(clojure.pprint/pprint @config) | |
(reset! pluspluses stored-pluspluses) | |
(reset! kick-talkbacks (@config :kick-talkbacks)) | |
(reset! parrot-talkbacks stored-parrot-lines) | |
(reset! talkbacks (@config :talkbacks)) | |
(.addShutdownHook (Runtime/getRuntime) (Thread. (fn [] (do (println "Going down with this ship...") | |
(with-open [wrtr (writer (format "%s/data/pluspluses.json" cwd))] | |
(.write wrtr (json/generate-string @pluspluses))) | |
(with-open [wrtr (writer (format "%s/data/parrot.json" cwd))] | |
(.write wrtr (json/generate-string @parrot-talkbacks { :pretty true }))))))) | |
(foo)))) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment