(ns wiktlarp.main (:require [clojure.java.io :as io] [cheshire.core :as json] [babashka.fs :as fs] [babashka.process :as p] [progrock.core :as prog] [clj-rcon.core :as rcon] [clojure.tools.logging :as l] [clojure.string :as str] [hawk.core :as hawk] [next.jdbc :as sql] [integrant.core :as ig]) (:gen-class)) (def chat-message-regexp #"(\*사망\* )?(.*?) : (.*)") (def ^:dynamic *rcon*) (def ^:dynamic *db*) (defmacro with-conn [[conn] & body] `(let [ds# (sql/get-datasource {:dbtype "sqlite" :dbname "db/words.db"})] (with-open [~conn (sql/get-connection ds#)] (sql/execute! ~conn ["select load_extension (?)" (System/getenv "SPELLFIX")]) ~@body))) (defn query-word [word] (->> (sql/execute! *db* ["select info from words where word = ?" word]) (map #(json/parse-string (:words/info %) keyword)))) (defn gloss-word [word] (->> word query-word (mapcat :senses) (mapcat (some-fn :raw_glosses :glosses)) first)) (defn parse-chat-message [x] (when-let [[_ _dead? author body] (re-matches chat-message-regexp x)] {:author author :body body})) (defn say [s] (->> s (take 128) ; truncate to 128 chars (apply str) (format "say \"%s\"") (rcon/exec *rcon*))) (defn find-word [s] (some-> (or (re-find #"\[\[([^]]+)]]" s) (re-find #"define\s+(\w+)" s)) second)) (defn do-definition [s] (when-some [w (find-word s)] (l/infof "looking up word: %s" w) (let [r (gloss-word w)] (if r (l/infof "found word! %s" r) (l/infof "no entry... %s" w)) (Thread/sleep 750) ; avoid ratelimit lol (say (format "%s: %s" w (or r "no entry found (u_u)")))))) (defn rcon-connect [host port password] (l/info "attempting rcon connection...") (or (try (let [c @(rcon/connect host port password)] @(rcon/exec c "echo \"wikilarper connected!\"") (l/info "connected to rcon!") c) (catch java.net.ConnectException e (l/info (ex-message e)) nil)) (do (Thread/sleep 5000) (recur host port password)))) (defn handle-console-event [s] (l/infof "! %s" s) (when-let [{:keys [author body]} (parse-chat-message s)] (do-definition body))) (def ^:dynamic *prev-size*) (defn console-handler [log-file] (let [prev @*prev-size* content (slurp log-file) size (count content)] (try (cond (< prev size) (-> content (subs prev) str/trim-newline handle-console-event) (< size prev) (l/warn "log file shrunk?") :else nil) (finally (reset! *prev-size* size))))) (def config {:rcon/connection {:ip "127.0.0.1" :port 27015 :password "monitor"} :database/connection {:dbtype "sqlite" :dbname "db/words.db"} :console/watcher {:log-file (-> (str "~/.local/share/Steam/steamapps/common/Team Fortress 2/" "tf/console.log") fs/expand-home fs/file) :handler (ig/ref :wikilinker/handler)} :wikilinker/handler {:rcon (ig/ref :rcon/connection) :db (ig/ref :database/connection)}}) (defmethod ig/init-key :rcon/connection [_ {:keys [ip port password]}] (rcon-connect ip port password)) (defmethod ig/halt-key! :rcon/connection [_ conn] (.close conn)) (defmethod ig/init-key :database/connection [_ dsinfo] (sql/get-datasource dsinfo)) (defmethod ig/halt-key! :database/connection [_ conn] (.close conn)) (defmethod ig/init-key :console/watcher [_ {:keys [log-file handler]}] (l/infof "watching file %s" log-file) (hawk/watch! [{:paths [(str log-file)] :handler (binding [*prev-size* (atom (if (fs/exists? log-file) (-> log-file slurp count) 0))] (bound-fn [_ctx ev] (try (handler log-file) (catch Exception e (l/errorf e "exception in console handler") (throw e)))))}])) (defmethod ig/halt-key! :console/watcher [_ watcher] (hawk/stop! watcher)) (defmethod ig/init-key :wikilinker/handler [_ {:keys [rcon db]}] (binding [*rcon* rcon *db* db] (bound-fn [log-file] (console-handler log-file)))) (defn -main [] (let [system (ig/init config)] (.addShutdownHook (Runtime/getRuntime) (Thread. #(do (ig/halt! system) (binding [*out* *err*] (println "shutting down")))))))