Files
wiktlarp-tf2/src/wiktlarp/main.clj
T
2026-08-07 09:54:27 -06:00

162 lines
4.8 KiB
Clojure

(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")))))))