idk push for pais
build / build (push) Failing after 2m16s

This commit is contained in:
2026-08-16 11:01:28 -06:00
parent ab77858eda
commit 47f213b9bc
19 changed files with 477 additions and 360 deletions
-4
View File
@@ -19,8 +19,4 @@ raw-wiktexttract-data.jsonl
*.db
raw-wiktextract-data.jsonlwords.db
words.db-journal
# Added by cargo
/target
Generated
+237 -5
View File
@@ -61,6 +61,12 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.13.1"
@@ -146,12 +152,44 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
[[package]]
name = "colog"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df62599ba6adc9c6c04a54278c8209125343dc4775f57b9d76c9a4287e58f2bd"
dependencies = [
"colored",
"env_logger",
"log",
]
[[package]]
name = "colorchoice"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
[[package]]
name = "colored"
version = "3.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34"
dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "console"
version = "0.16.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fe5f465a4f6fee88fad41b85d990f84c835335e85b5d9e6e63e0d06d28cba7c"
dependencies = [
"encode_unicode",
"libc",
"unicode-width",
"windows-sys 0.61.2",
]
[[package]]
name = "darling"
version = "0.21.3"
@@ -187,6 +225,37 @@ dependencies = [
"syn 2.0.119",
]
[[package]]
name = "defmt"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1"
dependencies = [
"bitflags 1.3.2",
"defmt-macros",
]
[[package]]
name = "defmt-macros"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8"
dependencies = [
"defmt-parser",
"proc-macro2",
"quote",
"syn 2.0.119",
]
[[package]]
name = "defmt-parser"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e"
dependencies = [
"thiserror",
]
[[package]]
name = "deranged"
version = "0.5.8"
@@ -203,6 +272,7 @@ dependencies = [
"downcast-rs",
"libsqlite3-sys",
"r2d2",
"serde_json",
"sqlite-wasm-rs",
"time",
]
@@ -276,6 +346,35 @@ version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d"
[[package]]
name = "encode_unicode"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
[[package]]
name = "env_filter"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "900d271a03799a1ee8d1ca9b19893b48ca674a9284fefcfb85f05e74ed314217"
dependencies = [
"log",
"regex",
]
[[package]]
name = "env_logger"
version = "0.11.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de671bd27a75a797dc9ae289ba1e77276e75e2026408aab65185384e2d5cd3f6"
dependencies = [
"anstream",
"anstyle",
"env_filter",
"jiff",
"log",
]
[[package]]
name = "equivalent"
version = "1.0.2"
@@ -333,6 +432,30 @@ dependencies = [
"libc",
]
[[package]]
name = "futures-core"
version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e"
[[package]]
name = "futures-task"
version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd"
[[package]]
name = "futures-util"
version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc"
dependencies = [
"futures-core",
"futures-task",
"pin-project-lite",
"slab",
]
[[package]]
name = "getrandom"
version = "0.2.17"
@@ -381,13 +504,26 @@ dependencies = [
"hashbrown 0.17.1",
]
[[package]]
name = "indicatif"
version = "0.18.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9433806cd6b4ec1aba79c021c7e4c58fb4c3b9977c085062e611ac929998fb0c"
dependencies = [
"console",
"portable-atomic",
"unicode-width",
"unit-prefix",
"web-time",
]
[[package]]
name = "inotify"
version = "0.11.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cc00ea907cab49550b7da656f80ebb97be1b997d931fbcd28d39734e17ce592"
dependencies = [
"bitflags",
"bitflags 2.13.1",
"inotify-sys",
"libc",
]
@@ -413,6 +549,42 @@ version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "jiff"
version = "0.2.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "668b7183bd07af9a4885f5c35b0cc5c83c4607a913c16b7e17291832910d2dcc"
dependencies = [
"defmt",
"jiff-core",
"jiff-static",
"log",
"portable-atomic",
"portable-atomic-util",
"serde_core",
]
[[package]]
name = "jiff-core"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7feca88439efe53da3754500c1851dedf3cb36c524dd5cf8225cc0794de95d09"
dependencies = [
"defmt",
]
[[package]]
name = "jiff-static"
version = "0.2.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a69dcb3a21cfb32ce1cd056169337ca284af0766dd766e7878819b251a49204"
dependencies = [
"jiff-core",
"proc-macro2",
"quote",
"syn 2.0.119",
]
[[package]]
name = "js-sys"
version = "0.3.104"
@@ -420,6 +592,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a"
dependencies = [
"cfg-if",
"futures-util",
"wasm-bindgen",
]
@@ -439,7 +612,7 @@ version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07293a4e297ac234359b510362495713f75ea345d5307140414f20c69ffeb087"
dependencies = [
"bitflags",
"bitflags 2.13.1",
"libc",
]
@@ -507,7 +680,7 @@ version = "8.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3"
dependencies = [
"bitflags",
"bitflags 2.13.1",
"fsevent-sys",
"inotify",
"kqueue",
@@ -525,7 +698,7 @@ version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42b8cfee0e339a0337359f3c88165702ac6e600dc01c0cc9579a92d62b08477a"
dependencies = [
"bitflags",
"bitflags 2.13.1",
]
[[package]]
@@ -596,6 +769,21 @@ version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548"
[[package]]
name = "portable-atomic"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85"
[[package]]
name = "portable-atomic-util"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618"
dependencies = [
"portable-atomic",
]
[[package]]
name = "powerfmt"
version = "0.2.0"
@@ -671,7 +859,7 @@ version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
dependencies = [
"bitflags",
"bitflags 2.13.1",
]
[[package]]
@@ -851,6 +1039,12 @@ dependencies = [
"libc",
]
[[package]]
name = "slab"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
[[package]]
name = "smallvec"
version = "1.15.2"
@@ -1008,6 +1202,17 @@ dependencies = [
"syn 3.0.3",
]
[[package]]
name = "tokio-stream"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3d06f0b082ba57c26b79407372e57cf2a1e28124f78e9479fe80322cf53420b"
dependencies = [
"futures-core",
"pin-project-lite",
"tokio",
]
[[package]]
name = "toml"
version = "0.8.23"
@@ -1086,12 +1291,24 @@ version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "unicode-width"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
[[package]]
name = "unicode-xid"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]]
name = "unit-prefix"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81e544489bf3d8ef66c953931f56617f423cd4b5494be343d9b9d3dda037b9a3"
[[package]]
name = "unsafe-libyaml"
version = "0.2.11"
@@ -1191,18 +1408,33 @@ dependencies = [
"tracing",
]
[[package]]
name = "web-time"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "wiktlarp"
version = "0.1.0"
dependencies = [
"clap",
"colog",
"diesel",
"indicatif",
"log",
"notify",
"rcon",
"regex",
"serde",
"serde_json",
"shellexpand",
"tokio",
"tokio-stream",
"watchfile",
"xdg",
]
+6 -1
View File
@@ -5,12 +5,17 @@ edition = "2024"
[dependencies]
clap = { version = "4.6.6", features = ["derive", "string"] }
diesel = { version = "2.3.12", features = ["r2d2", "sqlite"] }
colog = "1.4.0"
diesel = { version = "2.3.12", features = ["r2d2", "serde_json", "sqlite"] }
indicatif = "0.18.6"
log = "0.4.33"
notify = "8.2.0"
rcon = { version = "0.6.0", features = ["rt-tokio"] }
regex = "1.13.1"
serde = "1.0.229"
serde_json = "1.0.151"
shellexpand = { version = "3.1.2", features = ["path"] }
tokio = { version = "1.53.1", features = ["full"] }
tokio-stream = "0.1.19"
watchfile = "0.1.1"
xdg = "3.0.0"
-212
View File
@@ -1,212 +0,0 @@
(ns wiktlarp.main
(:require [clojure.java.io :as io]
[clojure.core.match :refer [match]]
[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])
(:import (java.time Instant Duration)
(java.time.temporal ChronoUnit))
(:gen-class))
(def chat-message-regexp #"(\*사망\* )?(.*?) : (.*)")
(def ^:dynamic *rcon*)
(def ^:dynamic *db*)
(def cooldown 3)
(def last-sent (atom (.minusSeconds (Instant/now) 3)))
(defmacro with-conn [[conn] & body]
`(let [ds# (sql/get-datasource
{:dbtype "sqlite"
:dbname "db/words.db"})]
(with-open [~conn (sql/get-connection ds#)]
~@body)))
(defn query-word [word]
(->> (sql/execute! *db* ["select info from words where word = ?"
word])
(map #(json/parse-string (:words/info %) keyword))))
(defn parse-fuckyou [x]
(cond (= x "peggle")
"(slang, Tiny Kitty Girl Pound) a video game, i think. idfk."
(and (re-find #"chimpanzee" x)
(re-find #"segway" x))
"(slang, Tiny Kitty Girl Pound) chimpanzeee sirdnway"
:else nil))
(defn gloss-word [word]
(if-some [fuckyou (parse-fuckyou word)]
fuckyou
(->> word
query-word
(mapcat :senses)
(mapcat (some-fn :raw_glosses :glosses))
first)))
(defn etymology-word [word]
(->> word
query-word
(map :etymology_text)
first))
(defn parse-chat-message [x]
(when-let [[_ _dead? author body]
(re-matches chat-message-regexp x)]
{:author author :body body}))
(defn say [s]
(let [now (Instant/now)]
(when (< (compare (Duration/ofSeconds 3)
(Duration/between @last-sent now))
0)
(reset! last-sent now)
(->> 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 find-etymology [s]
(some-> (or (and (re-find #"ety(?:m(?:ology)?)?" s)
(re-find #"\[\[([^]]+)]]" s))
(re-find #"etymology of \s+(\w+)" s))
second))
(defn parse-command [s]
(if-let [x (find-etymology s)]
[:etymology x]
(if-let [x (find-word s)]
[:define x]
nil)))
(defn do-definition [w]
(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 do-etymology [w]
(l/infof "looking up word: %s" w)
(let [r (etymology-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)]
(match (parse-command s)
[:define x] (do-definition x)
[:etymology x] (do-etymology x)
:else nil)))
(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")))))))
-17
View File
@@ -1,17 +0,0 @@
use diesel::prelude::*;
#[derive(Queryable, Selectable)]
#[diesel(table_name = crate::schema::words)]
#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
pub struct Word {
pub id : i32,
pub word : String,
pub info : String,
}
#[derive(Insertable)]
#[diesel(table_name = crate::schema::words)]
pub struct NewWord<'a> {
pub word : &'a str,
pub info : &'a str,
}
View File
+1 -1
View File
@@ -38,7 +38,7 @@
self.packages.${system}.db
self.packages.${system}.default
];
DATABASE_URL = "words.db";
DATABASE_URL = "db/words.db";
packages = with pkgs; [
(sqlite.override { interactive = true; })
python314Packages.wiktextract
+23 -23
View File
@@ -2,14 +2,14 @@ use regex::{regex, Regex};
#[derive(Debug,Eq,PartialEq,Clone)]
pub struct EntryQuery<'a> {
term : &'a str,
language : Option <&'a str>,
pos : Option <&'a str>,
pub term : &'a str,
pub language : Option <&'a str>,
pub pos : Option <&'a str>,
}
impl<'a> EntryQuery<'a> {
pub fn basic (term : &'a str) -> EntryQuery<'a> {
EntryQuery { term, language: None, pos: None }
EntryQuery { term, language: Some ("English"), pos: None }
}
}
@@ -17,6 +17,7 @@ impl<'a> EntryQuery<'a> {
pub enum Command<'a> {
Define (EntryQuery<'a>),
Etymology (EntryQuery<'a>),
Quit,
}
#[derive(Debug,Eq,PartialEq,Clone)]
@@ -26,10 +27,6 @@ pub struct ChatMessage<'a> {
}
impl<'a> ChatMessage<'a> {
pub fn unauthored (content : &'a str) -> ChatMessage<'a> {
ChatMessage { author: "", content }
}
pub fn parse (line : &'a str) -> Option<ChatMessage<'a>> {
let re : &Regex = regex! (r"(?:\*사망\* )?(.*?) : (.*)");
let r = re.captures (line)?;
@@ -40,26 +37,24 @@ impl<'a> ChatMessage<'a> {
}
}
fn find_query<'a> (s : &'a str) -> Option<EntryQuery<'a>> {
let basic_re = regex! (r"^\s*define (.*)|define (\w+)");
let wikilink_re = regex! (r"(?m)\[\[([^]]*)]]");
let term = if let Some (r) = wikilink_re.captures (s) {
r.get (1)
} else if let Some (r) = basic_re.captures (s) {
r.get (1).or (r.get (2))
} else {
None
};
Some (EntryQuery::basic (term?.as_str ()))
}
impl<'a> Command<'a> {
pub fn parse (msg : &'a ChatMessage<'a>) -> Option<Command<'a>> {
Self::parse_str (msg.content)
}
pub fn parse_str (s : &'a str) -> Option<Command<'a>> {
Self::parse_etymology (s).or (Self::parse_define (s))
Self::parse_quit (s)
.or (Self::parse_etymology (s))
.or (Self::parse_define (s))
}
fn parse_quit (s : &'a str) -> Option<Command<'a>> {
if s == "123 I Browsed The Source Code And Found \
The Funny Quit Command LOL!!!!!" {
Some (Command::Quit)
} else {
None
}
}
fn parse_define (s : &'a str) -> Option<Command<'a>> {
@@ -76,7 +71,9 @@ impl<'a> Command<'a> {
}
fn parse_etymology (s : &'a str) -> Option<Command<'a>> {
let basic_re = regex! (r"^\s*etymology of (.*)|ety(?:m(?:ology(?: of)?)?)? (\w+)");
let basic_re = regex! (
r"^\s*etymology of (.*)|ety(?:m(?:ology(?: of)?)?)? (\w+)"
);
let wikilink_re = regex! (r"ety(?:m(?:ology(?: of)?)?)? \[\[([^]]*)]]");
let term = if let Some (r) = wikilink_re.captures (s) {
r.get (1)
@@ -90,7 +87,10 @@ impl<'a> Command<'a> {
}
mod tests {
// 왜 이렇게 필요하니?
#[allow(unused_imports)]
use super::*;
#[allow(unused_imports)]
use super::Command as Cmd;
#[test]
+31 -33
View File
@@ -1,5 +1,7 @@
use notify::{Event, RecursiveMode, Result, Watcher};
use std::{fs::File, io::{Read as _, Seek as _, SeekFrom, pipe}, path::{Path, PathBuf}, sync::mpsc};
use tokio::sync::mpsc as tokio_mpsc;
use std::{fs::File, io::{Read as _, Seek as _, SeekFrom}, path::{Path, PathBuf}, sync::mpsc};
use crate::error;
pub fn default_log_file () -> PathBuf {
let base = xdg::BaseDirectories::new ()
@@ -9,10 +11,6 @@ pub fn default_log_file () -> PathBuf {
.to_path_buf ()
}
pub struct Console {
prev_size : usize,
}
fn file_size (file : &Path) -> u64 {
if let Ok (mut f) = File::open (file) {
let x = f.seek (SeekFrom::End (0));
@@ -22,37 +20,37 @@ fn file_size (file : &Path) -> u64 {
}
}
impl Console {
pub fn watch<F> (log_file : &Path, cb : F) -> Result<
// mpsc::Receiver<std::result::Result<Event, notify::Error>>
()
>
where F : Fn (&str) -> ()
{
let mut prev_size = file_size (log_file);
let (tx, rx) = mpsc::channel::<Result<Event>> ();
let mut watcher = notify::recommended_watcher (tx)?;
watcher.watch (log_file, RecursiveMode::NonRecursive)?;
for res in rx {
if let Err (err) = res {
log::error! ("{:?}", err);
continue
};
pub async fn watch (
log_file : &Path,
txr : tokio_mpsc::Sender<String>,
) -> error::Result<()> {
let mut prev_size = file_size (log_file);
let (tx, rx) = mpsc::channel::<Result<Event>> ();
let mut watcher = notify::recommended_watcher (tx)
.map_err (error::Error::Notify)?;
watcher.watch (log_file, RecursiveMode::NonRecursive)
.map_err (error::Error::Notify)?;
for res in rx {
if let Err (err) = res {
log::error! ("{:?}", err);
continue
};
let new_size = file_size (log_file);
let new_size = file_size (log_file);
if prev_size < new_size {
let mut f = File::open (log_file)
.expect ("failed to read log file");
f.seek (SeekFrom::Start (prev_size)).unwrap ();
let mut buf = String::new ();
f.read_to_string (&mut buf).unwrap ();
cb (&buf)
} if new_size < prev_size {
log::warn! ("log file shrank!??");
if prev_size < new_size {
let mut f = File::open (log_file)
.expect ("failed to read log file");
f.seek (SeekFrom::Start (prev_size)).unwrap ();
let mut buf = String::new ();
f.read_to_string (&mut buf).expect ("utf-8 error?");
for l in buf.lines () {
txr.send (l.to_string ()).await.unwrap ();
}
prev_size = new_size;
} if new_size < prev_size {
log::warn! ("log file shrank!??");
}
Ok (())
prev_size = new_size;
}
Ok (())
}
+26 -34
View File
@@ -1,25 +1,32 @@
pub mod models;
pub mod schema;
use diesel::prelude::*;
use diesel::connection::SimpleConnection;
use diesel::r2d2::{ConnectionManager, CustomizeConnection, Pool};
use std::env;
use crate::models::*;
use models::*;
#[derive(Debug)]
pub struct Fuck ();
fn init_conn (conn : &mut SqliteConnection) -> Result<(), diesel::r2d2::Error> {
conn.batch_execute ("
PRAGMA busy_timeout = 15000;
PRAGMA journal_mode = WAL;
PRAGMA synchronous = NORMAL;
PRAGMA wal_autocheckpoint = 1000;
PRAGMA wal_checkpoint(TRUNCATE);
PRAGMA cache_size=-2000000
")?;
Ok (())
}
impl CustomizeConnection<SqliteConnection, diesel::r2d2::Error> for Fuck {
fn on_acquire (&self, conn: &mut SqliteConnection) -> Result<
fn on_acquire (&self, conn : &mut SqliteConnection) -> Result<
(), diesel::r2d2::Error
> {
conn.batch_execute ("
PRAGMA busy_timeout = 15000;
PRAGMA journal_mode = WAL;
PRAGMA synchronous = NORMAL;
PRAGMA wal_autocheckpoint = 1000;
PRAGMA wal_checkpoint(TRUNCATE);
PRAGMA cache_size=-2000000
").unwrap ();
Ok (())
init_conn (conn)
}
fn on_release (&self, _conn: SqliteConnection) {}
@@ -41,30 +48,18 @@ pub fn connect () -> SqliteConnection {
.expect ("DATABASE_URL must be set");
let mut conn = SqliteConnection::establish (&database_url)
.unwrap_or_else (|_| panic! ("Error connecting to {}", database_url));
conn.batch_execute ("
PRAGMA journal_mode = WAL; -- better write-concurrency
PRAGMA synchronous = NORMAL; -- fsync only in critical moments
PRAGMA wal_autocheckpoint = 1000; -- write WAL changes back every 1000 pages, for an in average 1MB WAL file. May affect readers if number is increased
PRAGMA wal_checkpoint(TRUNCATE); -- free some space by truncating possibly massive WAL files from the last run.
PRAGMA busy_timeout = 5000; -- sleep if the database is busy
PRAGMA cache_size=-2000000
").unwrap ();
init_conn (&mut conn).unwrap ();
conn
}
pub fn get_word (w : &str) -> Vec<Word> {
let conn = &mut connect ();
use crate::schema::words::dsl::*;
let results = words
use schema::words::dsl::*;
words
.filter (word.eq (w))
.limit (5)
.select (Word::as_select ())
.load (conn)
.expect ("error loading words");
for wd in results {
println! ("{}", wd.word);
}
todo! ()
.expect ("error loading words")
}
pub fn entry_as_word (entry : &serde_json::Value) -> Option<&str> {
@@ -73,14 +68,11 @@ pub fn entry_as_word (entry : &serde_json::Value) -> Option<&str> {
pub fn insert_word (
conn : &mut SqliteConnection,
entry : serde_json::Value,
info : &serde_json::Value,
) -> Option<()> {
use crate::schema::words;
let word = entry_as_word (&entry)?;
let new_word = NewWord {
word,
info: &entry.to_string (),
};
use schema::words;
let word = entry_as_word (info)?;
let new_word = NewWord { word, info };
diesel::insert_into (words::table)
.values (new_word)
.execute (conn).map_or_else (
+34
View File
@@ -0,0 +1,34 @@
use diesel::prelude::*;
use serde_json as json;
#[derive(Queryable, Selectable, Debug)]
#[diesel(table_name = crate::db::schema::words)]
#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
pub struct Word {
pub id : i32,
pub word : String,
pub info : json::Value,
}
#[derive(Insertable,Debug)]
#[diesel(table_name = crate::db::schema::words)]
pub struct NewWord<'a> {
pub word : &'a str,
pub info : &'a json::Value,
}
impl Word {
pub fn glosses (&self) -> Vec<String> {
self.info["senses"].as_array ().unwrap ()
.iter ()
.flat_map (|x| {
x.get ("raw_glosses")
.map (|y| y.as_array ().unwrap ().clone ())
.or (x.get ("glosses")
.map (|y| y.as_array ().unwrap ().clone ()))
.unwrap_or (Vec::default ())
})
.map (|x| x.as_str ().unwrap ().to_string ())
.collect ()
}
}
+1 -1
View File
@@ -4,6 +4,6 @@ diesel::table! {
words (id) {
id -> Integer,
word -> Text,
info -> Text,
info -> Json,
}
}
+7
View File
@@ -0,0 +1,7 @@
#[derive(Debug)]
pub enum Error {
Rcon (::rcon::Error),
Notify (::notify::Error),
}
pub type Result<T> = core::result::Result<T, Error>;
+62 -29
View File
@@ -1,21 +1,18 @@
mod console;
mod command;
mod rcon;
mod error;
mod db;
use command::{ChatMessage, Command};
use rcon::{Connection, Error};
use std::{net::{SocketAddr}, path::{Path, PathBuf}, sync::mpsc, time::Duration};
use tokio::net::TcpStream;
use notify::{Event, RecursiveMode, Result, Watcher};
use command::{ChatMessage, Command, EntryQuery};
use rcon::Rcon;
use std::{net::SocketAddr, path::PathBuf};
use tokio::sync::mpsc as mpsc;
use clap::Parser;
use console::Console;
// const log_file : Path = Path::new (
// "~/.local/share/Steam/steamapps/common/Team Fortress 2/tf/console.log"
// );
/// Larp as Wiktionary in TF2's in-game chat.
#[derive(Parser, Debug)]
#[command(
/// LARP as Wiktionary in TF2's in-game chat.
#[derive (Parser, Debug)]
#[command (
name = "wiktlarp",
version,
about,
@@ -23,7 +20,9 @@ use console::Console;
)]
struct Args {
/// Path to TF2's console log
#[clap(
///
/// This must match the value of TF2's `con_logfile` setting.
#[clap (
value_parser,
long,
short='f',
@@ -31,8 +30,8 @@ struct Args {
)]
log_file: PathBuf,
/// RCON address to send chat commands to
#[clap(
/// RCON address to which chat commands are sent
#[clap (
value_parser,
long,
short='a',
@@ -41,28 +40,62 @@ struct Args {
rcon_address: SocketAddr,
/// RCON password
#[clap(value_parser,long,short='p',default_value="monitor")]
#[clap (value_parser,long,short='p',default_value="monitor")]
rcon_password: String,
}
#[tokio::main]
async fn main () -> Result<()> {
async fn main () -> error::Result<()> {
colog::init();
let args = Args::parse ();
println! ("{:?}", args);
let mut client = <Connection<TcpStream>>::builder ()
.connect (args.rcon_address.to_string (), &args.rcon_password)
.await.unwrap ();
let (tx, mut rx) = mpsc::channel::<String> (32);
let mut rcon = Rcon::connect (
args.rcon_address, &args.rcon_password
).await?;
Console::watch (
&args.log_file,
|line| {
println! ("line: {line}");
let Some (msg) = ChatMessage::parse (line) else { return };
let Some (cmd) = Command::parse (&msg) else { return };
println! ("cmd: {:?}", cmd);
tokio::spawn (async move {
console::watch (
&args.log_file,
tx
).await.unwrap ();
});
while let Some (line) = rx.recv ().await {
println! ("line: {line}");
let Some (msg) = ChatMessage::parse (&line) else { continue };
let Some (cmd) = Command::parse (&msg) else { continue };
println! ("cmd: {:?}", cmd);
// wait a moment to avoid being ratelimitted in the case that
// we are responding to our own message.
tokio::time::sleep (
tokio::time::Duration::from_millis (750)
).await;
match cmd {
Command::Define (EntryQuery {term,language,..}) => {
let fuckyou9000 = db::get_word (term);
let w = fuckyou9000
.iter ()
.filter (|x| language.is_none ()
|| x.info["lang"].as_str () == language)
.flat_map (|x| x.glosses ())
.nth (0)
.unwrap_or (
"no entry found (u_u) contribute it! };3".to_string ()
);
let mut s = format! ("{term}: {:?}", w);
s.truncate (127); // fuck you
rcon.say (&s).await.unwrap ();
},
Command::Etymology (entry_query) => {
todo! ()
}
Command::Quit => {
rcon.cmd ("disconnect").await.unwrap ();
}
}
)?;
}
Ok (())
}
+49
View File
@@ -0,0 +1,49 @@
use crate::error;
use ::rcon::Connection;
use std::net::SocketAddr;
use tokio::net::TcpStream;
/// An RCON connection config. Methods will lazily (re)connect
pub struct Rcon {
conn : Connection<TcpStream>
}
impl AsRef<Connection<TcpStream>> for Rcon {
fn as_ref (&self) -> &Connection<TcpStream> {
&self.conn
}
}
impl Rcon {
pub async fn connect (
address : SocketAddr,
password : &str
) -> error::Result<Rcon> {
let r = <Connection<TcpStream>>::builder ()
.connect (address, password)
.await;
match r {
Ok (conn) => {
log::info! ("connected to rcon");
tokio::time::sleep (
tokio::time::Duration::from_millis (500)
).await;
Ok (Rcon { conn })
},
Err (e) => Err (error::Error::Rcon (e)),
}
}
pub async fn cmd (&mut self, s : &str) -> error::Result<String> {
self.conn.cmd (s).await.map_err (error::Error::Rcon)
}
pub async fn say (&mut self, s : &str) -> error::Result<String> {
if s.contains ("\"") && s.contains ("quit") {
self.cmd ("say \"try harder pal!!!!!\"").await
} else {
let probably_safe_s = s.replace("\"", "'");
self.cmd (&format! ("say \"{probably_safe_s}\"")).await
}
}
}