refactor to use futures for readers

- remove java interop where there are clojure fns available
- keyword destructuring where possible
- reorder fns to avoid forward ref
- naming to desinguish between connections and connection
atoms (conn/conn*)
- fns pass through connections to support
use with threading macros
This commit is contained in:
Looke Z 2023-11-05 17:57:23 +00:00
parent fe2916fa48
commit 6c5f7c4dc7
1 changed files with 99 additions and 57 deletions

View File

@ -1,84 +1,126 @@
(ns irc-bot.core
(:require [clojure.java.io :as io])
(:import (javax.net.ssl SSLSocketFactory)
(java.net Socket SocketException)
(java.io PrintWriter InputStreamReader BufferedReader)))
(java.net Socket SocketException)))
(def servers [{:name "irc.hashbang.sh" :port 6697 :tls true
(def servers [{:hostname "irc.hashbang.sh"
:port 6697
:tls true
:channels ["#!" "#bots"]}
{:name "testnet.ergo.chat" :port 6667 :tls false
{:hostname "testnet.ergo.chat"
:port 6667
:tls false
:channels ["#bots" "#0"]}
{:name "irc.tilde.chat" :port 6697 :tls true
{:hostname "irc.tilde.chat"
:port 6697
:tls true
:channels ["#bots" "#meta"]}
{:name "irc.libera.chat" :port 6667 :tls false
{:hostname "irc.libera.chat"
:port 6667
:tls false
:channels ["#bots" "#clojure"]}])
(def user {:name "Clojure Bot" :nick "cljpt"})
(def user {:username "Clojure Bot"
:nick "cljpt"})
(declare conn-handler)
(defn socket->ssl-socket
[socket hostname port]
(.createSocket
(SSLSocketFactory/getDefault)
socket
hostname port true))
(defn connect [server]
(let [socket (if (:tls server)
(.createSocket (SSLSocketFactory/getDefault)
(Socket. (:name server) (:port server))
(:name server) (:port server) true)
(Socket. (:name server) (:port server)))
channels (:channels server)
in (BufferedReader. (InputStreamReader. (.getInputStream socket)))
out (PrintWriter. (.getOutputStream socket))
conn (atom {:in in :out out :socket socket :channels channels})]
(doto (Thread. #(try (conn-handler conn)
(catch SocketException se
(println (str "Caught exception: " (.getMessage se))))))
(.start))
conn))
(defn conn->address-str [conn]
(-> conn
:socket
.getRemoteSocketAddress
.toString))
(defn write [conn msg]
(doto (:out @conn)
(.println (str msg "\r"))
(.flush)))
(defn disconnect [connection-atoms]
(doseq [conn* connection-atoms]
(if (-> @conn* :socket .isClosed)
(println "No established connection to:"
(conn->address-str @conn*))
(do
(println "Closing TCP socket connection:"
(conn->address-str @conn*))
(-> @conn* :socket .close)))))
(defn write [{:keys [writer] :as _conn} msg]
(.write writer (str msg "\n"))
(.flush writer))
(defn join [conn chan]
(write conn (str "JOIN " chan)))
(defn login [conn user]
(write conn (str "NICK " (:nick user)))
(write conn (str "USER " (:nick user) " 0 * :" (:name user))))
(defn join-all-channels [connection-atoms]
(doseq [conn* connection-atoms
ch (:channels @conn*)]
(join @conn* ch))
connection-atoms)
(defn connected? [conn] (.isConnected conn))
(defn login [conn*
{:keys [nick username]
:as _user}]
(println "logging " username " into "
(conn->address-str @conn*))
(write @conn* (str "NICK " nick))
(write @conn* (str "USER " nick " 0 * :" username))
conn*)
(defn conn-handler [conn]
(println "Connecting to:" (.toString (.getRemoteSocketAddress (:socket @conn))))
(login conn user)
(while (and (connected? (:socket @conn)) (nil? (:exit @conn)))
(let [msg (.readLine (:in @conn))]
(println msg)
(cond
(re-find #"^ERROR :Closing Link:" msg)
(swap! conn merge {:exit true})
(re-find #"^PING" msg)
(write conn (str "PONG " (re-find #":.*" msg)))))))
(defn read-messages [conn*]
(with-open [socket (:socket @conn*)
reader (io/reader socket)]
(swap! conn* assoc :reader reader)
(doseq [line (line-seq reader)]
(cond
(re-find #"^ERROR :Closing Link:" line)
(swap! conn* merge {:exit true})
(defn disconnected? [conn] (.isClosed conn))
(re-find #"^PING" line)
(write conn* (str "PONG " (re-find #":.*" line))))
(defn disconnect-servers [conn]
(doseq [s conn]
(if (not (disconnected? (:socket @s)))
(do
(println "Closing TCP socket connection:" (.toString (.getRemoteSocketAddress (:socket @s))))
(.close (:socket @s)))
(println "No established connection to:" (.toString (.getRemoteSocketAddress (:socket @s)))))))
;else
(println line))))
(defn join-all-channels [conns]
(for [c conns
ch (:channels @c)]
(join c ch)))
(defn spawn-message-reader [conn*]
(future
(try
(read-messages conn*)
(catch SocketException e
(println "Exception : "
(conn->address-str conn*)
" : " e))))
conn*)
(defn server->connection
[{:keys [port tls hostname]
:as server}]
(let [socket (cond-> (Socket. hostname port)
tls
(socket->ssl-socket hostname port))]
(atom
(merge server
{:socket socket
:writer (io/writer socket :append true)}))))
(defn connect-and-login-all-servers []
(let [connection-atoms (map server->connection servers)]
(doseq [c connection-atoms]
(-> c
(login user)
spawn-message-reader))
connection-atoms))
(comment
(def connections (doall (map connect servers)))
(disconnect-servers connections)
(def connections-atoms
(connect-and-login-all-servers))
(join-all-channels connections)
(disconnect connections-atoms)
(join-all-channels connections-atoms)
,)