Compare commits
3
Commits
main
..
7379cffc03
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7379cffc03 | ||
|
|
6a864e1e89 | ||
|
|
f4a413c362 |
@@ -19,3 +19,8 @@ raw-wiktexttract-data.jsonl
|
|||||||
*.db
|
*.db
|
||||||
raw-wiktextract-data.jsonlwords.db
|
raw-wiktextract-data.jsonlwords.db
|
||||||
words.db-journal
|
words.db-journal
|
||||||
|
|
||||||
|
|
||||||
|
# Added by cargo
|
||||||
|
|
||||||
|
/target
|
||||||
|
|||||||
Generated
+1327
File diff suppressed because it is too large
Load Diff
+16
@@ -0,0 +1,16 @@
|
|||||||
|
[package]
|
||||||
|
name = "wiktlarp"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
clap = { version = "4.6.6", features = ["derive", "string"] }
|
||||||
|
diesel = { version = "2.3.12", features = ["r2d2", "sqlite"] }
|
||||||
|
log = "0.4.33"
|
||||||
|
notify = "8.2.0"
|
||||||
|
rcon = { version = "0.6.0", features = ["rt-tokio"] }
|
||||||
|
regex = "1.13.1"
|
||||||
|
shellexpand = { version = "3.1.2", features = ["path"] }
|
||||||
|
tokio = { version = "1.53.1", features = ["full"] }
|
||||||
|
watchfile = "0.1.1"
|
||||||
|
xdg = "3.0.0"
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
(ns wiktlarp.main
|
(ns wiktlarp.main
|
||||||
(:require [clojure.java.io :as io]
|
(:require [clojure.java.io :as io]
|
||||||
|
[clojure.core.match :refer [match]]
|
||||||
[cheshire.core :as json]
|
[cheshire.core :as json]
|
||||||
[babashka.fs :as fs]
|
[babashka.fs :as fs]
|
||||||
[babashka.process :as p]
|
[babashka.process :as p]
|
||||||
@@ -10,6 +11,8 @@
|
|||||||
[hawk.core :as hawk]
|
[hawk.core :as hawk]
|
||||||
[next.jdbc :as sql]
|
[next.jdbc :as sql]
|
||||||
[integrant.core :as ig])
|
[integrant.core :as ig])
|
||||||
|
(:import (java.time Instant Duration)
|
||||||
|
(java.time.temporal ChronoUnit))
|
||||||
(:gen-class))
|
(:gen-class))
|
||||||
|
|
||||||
(def chat-message-regexp #"(\*사망\* )?(.*?) : (.*)")
|
(def chat-message-regexp #"(\*사망\* )?(.*?) : (.*)")
|
||||||
@@ -18,13 +21,15 @@
|
|||||||
|
|
||||||
(def ^:dynamic *db*)
|
(def ^:dynamic *db*)
|
||||||
|
|
||||||
|
(def cooldown 3)
|
||||||
|
|
||||||
|
(def last-sent (atom (.minusSeconds (Instant/now) 3)))
|
||||||
|
|
||||||
(defmacro with-conn [[conn] & body]
|
(defmacro with-conn [[conn] & body]
|
||||||
`(let [ds# (sql/get-datasource
|
`(let [ds# (sql/get-datasource
|
||||||
{:dbtype "sqlite"
|
{:dbtype "sqlite"
|
||||||
:dbname "db/words.db"})]
|
:dbname "db/words.db"})]
|
||||||
(with-open [~conn (sql/get-connection ds#)]
|
(with-open [~conn (sql/get-connection ds#)]
|
||||||
(sql/execute! ~conn ["select load_extension (?)"
|
|
||||||
(System/getenv "SPELLFIX")])
|
|
||||||
~@body)))
|
~@body)))
|
||||||
|
|
||||||
(defn query-word [word]
|
(defn query-word [word]
|
||||||
@@ -32,11 +37,27 @@
|
|||||||
word])
|
word])
|
||||||
(map #(json/parse-string (:words/info %) keyword))))
|
(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]
|
(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
|
(->> word
|
||||||
query-word
|
query-word
|
||||||
(mapcat :senses)
|
(map :etymology_text)
|
||||||
(mapcat (some-fn :raw_glosses :glosses))
|
|
||||||
first))
|
first))
|
||||||
|
|
||||||
(defn parse-chat-message [x]
|
(defn parse-chat-message [x]
|
||||||
@@ -45,27 +66,54 @@
|
|||||||
{:author author :body body}))
|
{:author author :body body}))
|
||||||
|
|
||||||
(defn say [s]
|
(defn say [s]
|
||||||
(->> s
|
(let [now (Instant/now)]
|
||||||
(take 128) ; truncate to 128 chars
|
(when (< (compare (Duration/ofSeconds 3)
|
||||||
(apply str)
|
(Duration/between @last-sent now))
|
||||||
(format "say \"%s\"")
|
0)
|
||||||
(rcon/exec *rcon*)))
|
(reset! last-sent now)
|
||||||
|
(->> s
|
||||||
|
(take 128) ; truncate to 128 chars
|
||||||
|
(apply str)
|
||||||
|
(format "say \"%s\"")
|
||||||
|
(rcon/exec *rcon*)))))
|
||||||
|
|
||||||
(defn find-word [s]
|
(defn find-word [s]
|
||||||
(some-> (or (re-find #"\[\[([^]]+)]]" s)
|
(some-> (or (re-find #"\[\[([^]]+)]]" s)
|
||||||
(re-find #"define\s+(\w+)" s))
|
(re-find #"define\s+(\w+)" s))
|
||||||
second))
|
second))
|
||||||
|
|
||||||
(defn do-definition [s]
|
(defn find-etymology [s]
|
||||||
(when-some [w (find-word s)]
|
(some-> (or (and (re-find #"ety(?:m(?:ology)?)?" s)
|
||||||
(l/infof "looking up word: %s" w)
|
(re-find #"\[\[([^]]+)]]" s))
|
||||||
(let [r (gloss-word w)]
|
(re-find #"etymology of \s+(\w+)" s))
|
||||||
(if r
|
second))
|
||||||
(l/infof "found word! %s" r)
|
|
||||||
(l/infof "no entry... %s" w))
|
(defn parse-command [s]
|
||||||
(Thread/sleep 750) ; avoid ratelimit lol
|
(if-let [x (find-etymology s)]
|
||||||
(say (format "%s: %s"
|
[:etymology x]
|
||||||
w (or r "no entry found (u_u)"))))))
|
(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]
|
(defn rcon-connect [host port password]
|
||||||
(l/info "attempting rcon connection...")
|
(l/info "attempting rcon connection...")
|
||||||
@@ -82,7 +130,10 @@
|
|||||||
(defn handle-console-event [s]
|
(defn handle-console-event [s]
|
||||||
(l/infof "! %s" s)
|
(l/infof "! %s" s)
|
||||||
(when-let [{:keys [author body]} (parse-chat-message s)]
|
(when-let [{:keys [author body]} (parse-chat-message s)]
|
||||||
(do-definition body)))
|
(match (parse-command s)
|
||||||
|
[:define x] (do-definition x)
|
||||||
|
[:etymology x] (do-etymology x)
|
||||||
|
:else nil)))
|
||||||
|
|
||||||
(def ^:dynamic *prev-size*)
|
(def ^:dynamic *prev-size*)
|
||||||
|
|
||||||
+6
-24
@@ -1,27 +1,9 @@
|
|||||||
{ mkCljBin
|
{ rustPlatform
|
||||||
, fake-git
|
|
||||||
, lib
|
|
||||||
, mkGraalBin
|
|
||||||
}:
|
}:
|
||||||
|
|
||||||
let
|
rustPlatform.buildRustPackage (finalAttrs: {
|
||||||
# mkCljBin sans fake-git. We don't need it, and I don't want it in
|
pname = "wiktlarp-tf2";
|
||||||
# my dev shell.
|
|
||||||
mkCljBin' = args: (mkCljBin args).overrideAttrs (final: prev: {
|
|
||||||
nativeBuildInputs =
|
|
||||||
builtins.filter
|
|
||||||
# A possibly-sketchy predicate, lol.
|
|
||||||
(x: x != fake-git)
|
|
||||||
prev.nativeBuildInputs;
|
|
||||||
});
|
|
||||||
|
|
||||||
# bin-path = lib.makeBinPath [
|
|
||||||
# sqlite3
|
|
||||||
# ];
|
|
||||||
in mkCljBin' {
|
|
||||||
name = "wiktlarp";
|
|
||||||
version = "0.1.0";
|
version = "0.1.0";
|
||||||
projectSrc = lib.cleanSource ./.;
|
src = ./.;
|
||||||
lockfile = ./deps-lock.json;
|
cargoLock.lockFile = ./Cargo.lock;
|
||||||
main-ns = "wiktlarp.main";
|
})
|
||||||
}
|
|
||||||
|
|||||||
Generated
+1
-132
@@ -1,115 +1,6 @@
|
|||||||
{
|
{
|
||||||
"nodes": {
|
"nodes": {
|
||||||
"clj-nix": {
|
|
||||||
"inputs": {
|
|
||||||
"devshell": "devshell",
|
|
||||||
"nix-fetcher-data": "nix-fetcher-data",
|
|
||||||
"nixpkgs": "nixpkgs"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1773151887,
|
|
||||||
"narHash": "sha256-YUiehwe2iTlwYSrnJ1pcw7KDNX+U42xlTx/2k/mo0P8=",
|
|
||||||
"owner": "jlesquembre",
|
|
||||||
"repo": "clj-nix",
|
|
||||||
"rev": "27dac4466c9d3939f6a4925bc09e0cb1d8f32d9c",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "jlesquembre",
|
|
||||||
"repo": "clj-nix",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"devshell": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": [
|
|
||||||
"clj-nix",
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1768818222,
|
|
||||||
"narHash": "sha256-460jc0+CZfyaO8+w8JNtlClB2n4ui1RbHfPTLkpwhU8=",
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "devshell",
|
|
||||||
"rev": "255a2b1725a20d060f566e4755dbf571bbbb5f76",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "devshell",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"flake-parts": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs-lib": "nixpkgs-lib"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1719745305,
|
|
||||||
"narHash": "sha256-xwgjVUpqSviudEkpQnioeez1Uo2wzrsMaJKJClh+Bls=",
|
|
||||||
"owner": "hercules-ci",
|
|
||||||
"repo": "flake-parts",
|
|
||||||
"rev": "c3c5ecc05edc7dafba779c6c1a61cd08ac6583e9",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "hercules-ci",
|
|
||||||
"repo": "flake-parts",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nix-fetcher-data": {
|
|
||||||
"inputs": {
|
|
||||||
"flake-parts": "flake-parts",
|
|
||||||
"nixpkgs": [
|
|
||||||
"clj-nix",
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1755022803,
|
|
||||||
"narHash": "sha256-/QtBdVfZlrRJW5enUoWlBE2wrLXJBMJ45X0rZh0jiaU=",
|
|
||||||
"owner": "jlesquembre",
|
|
||||||
"repo": "nix-fetcher-data",
|
|
||||||
"rev": "9da3926b1459d6ff15268072d1c51351b82811b9",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "jlesquembre",
|
|
||||||
"repo": "nix-fetcher-data",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
|
||||||
"lastModified": 1772773019,
|
|
||||||
"narHash": "sha256-E1bxHxNKfDoQUuvriG71+f+s/NT0qWkImXsYZNFFfCs=",
|
|
||||||
"owner": "NixOS",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "aca4d95fce4914b3892661bcb80b8087293536c6",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "NixOS",
|
|
||||||
"ref": "nixos-unstable",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs-lib": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1717284937,
|
|
||||||
"narHash": "sha256-lIbdfCsf8LMFloheeE6N31+BMIeixqyQWbSr2vk79EQ=",
|
|
||||||
"type": "tarball",
|
|
||||||
"url": "https://github.com/NixOS/nixpkgs/archive/eb9ceca17df2ea50a250b6b27f7bf6ab0186f198.tar.gz"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"type": "tarball",
|
|
||||||
"url": "https://github.com/NixOS/nixpkgs/archive/eb9ceca17df2ea50a250b6b27f7bf6ab0186f198.tar.gz"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs_2": {
|
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1775095191,
|
"lastModified": 1775095191,
|
||||||
"narHash": "sha256-CsqRiYbgQyv01LS0NlC7shwzhDhjNDQSrhBX8VuD3nM=",
|
"narHash": "sha256-CsqRiYbgQyv01LS0NlC7shwzhDhjNDQSrhBX8VuD3nM=",
|
||||||
@@ -127,29 +18,7 @@
|
|||||||
},
|
},
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"clj-nix": "clj-nix",
|
"nixpkgs": "nixpkgs"
|
||||||
"nixpkgs": "nixpkgs_2",
|
|
||||||
"sydpkgs": "sydpkgs"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"sydpkgs": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": [
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1775943619,
|
|
||||||
"narHash": "sha256-UwowTI+MSA4SD+Oebxnj02vAou8GNjvZYrCQBwKe5q0=",
|
|
||||||
"ref": "refs/heads/main",
|
|
||||||
"rev": "e44c957573baa6a196e8de107cfc5bba31caf29a",
|
|
||||||
"revCount": 39,
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://git.deertopia.net/msyds/sydpkgs"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://git.deertopia.net/msyds/sydpkgs"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,14 +1,9 @@
|
|||||||
{
|
{
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||||
clj-nix.url = "github:jlesquembre/clj-nix";
|
|
||||||
sydpkgs = {
|
|
||||||
url = "git+https://git.deertopia.net/msyds/sydpkgs";
|
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = { self, nixpkgs, clj-nix, ... }@inputs:
|
outputs = { self, nixpkgs, ... }@inputs:
|
||||||
let
|
let
|
||||||
supportedSystems = [
|
supportedSystems = [
|
||||||
"aarch64-darwin"
|
"aarch64-darwin"
|
||||||
@@ -20,9 +15,6 @@
|
|||||||
each-system = f: nixpkgs.lib.genAttrs supportedSystems (system: f rec {
|
each-system = f: nixpkgs.lib.genAttrs supportedSystems (system: f rec {
|
||||||
pkgs = import nixpkgs {
|
pkgs = import nixpkgs {
|
||||||
inherit system;
|
inherit system;
|
||||||
overlays = [
|
|
||||||
clj-nix.overlays.default
|
|
||||||
];
|
|
||||||
};
|
};
|
||||||
inherit (pkgs) lib;
|
inherit (pkgs) lib;
|
||||||
inherit system;
|
inherit system;
|
||||||
@@ -38,13 +30,12 @@
|
|||||||
|
|
||||||
devShells = each-system ({ pkgs, system, ... }: {
|
devShells = each-system ({ pkgs, system, ... }: {
|
||||||
default = pkgs.mkShell {
|
default = pkgs.mkShell {
|
||||||
inputsFrom = [ self.packages.${system}.db ];
|
inputsFrom = [
|
||||||
|
self.packages.${system}.db
|
||||||
|
self.packages.${system}.default
|
||||||
|
];
|
||||||
DATABASE_URL = "words.db";
|
DATABASE_URL = "words.db";
|
||||||
packages = with pkgs; [
|
packages = with pkgs; [
|
||||||
zprint
|
|
||||||
clojure
|
|
||||||
babashka
|
|
||||||
python3
|
|
||||||
(sqlite.override { interactive = true; })
|
(sqlite.override { interactive = true; })
|
||||||
python314Packages.wiktextract
|
python314Packages.wiktextract
|
||||||
sqlite-web
|
sqlite-web
|
||||||
|
|||||||
@@ -0,0 +1,159 @@
|
|||||||
|
use regex::{regex, Regex};
|
||||||
|
|
||||||
|
#[derive(Debug,Eq,PartialEq,Clone)]
|
||||||
|
pub struct EntryQuery<'a> {
|
||||||
|
term : &'a str,
|
||||||
|
language : Option <&'a str>,
|
||||||
|
pos : Option <&'a str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> EntryQuery<'a> {
|
||||||
|
pub fn basic (term : &'a str) -> EntryQuery<'a> {
|
||||||
|
EntryQuery { term, language: None, pos: None }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug,Eq,PartialEq,Clone)]
|
||||||
|
pub enum Command<'a> {
|
||||||
|
Define (EntryQuery<'a>),
|
||||||
|
Etymology (EntryQuery<'a>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug,Eq,PartialEq,Clone)]
|
||||||
|
pub struct ChatMessage<'a> {
|
||||||
|
author : &'a str,
|
||||||
|
content : &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
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)?;
|
||||||
|
Some (ChatMessage {
|
||||||
|
author: r.get (1)?.as_str (),
|
||||||
|
content: r.get (2)?.as_str (),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_define (s : &'a str) -> Option<Command<'a>> {
|
||||||
|
let basic_re = regex! (r"^\s*define (.*)|define (\w+)");
|
||||||
|
let wikilink_re = regex! (r"\[\[([^]]*)]]");
|
||||||
|
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 (Command::Define (EntryQuery::basic (term?.as_str ())))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_etymology (s : &'a str) -> Option<Command<'a>> {
|
||||||
|
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)
|
||||||
|
} else if let Some (r) = basic_re.captures (s) {
|
||||||
|
r.get (1).or (r.get (2))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
Some (Command::Etymology (EntryQuery::basic (term?.as_str ())))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use super::Command as Cmd;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn etym_wikilink () {
|
||||||
|
assert_eq! (
|
||||||
|
Cmd::parse_str (
|
||||||
|
"blah blah etymology of [[lexicography]]"
|
||||||
|
),
|
||||||
|
Some (Cmd::Etymology (EntryQuery::basic ("lexicography")))
|
||||||
|
);
|
||||||
|
assert_eq! (
|
||||||
|
Cmd::parse_str (
|
||||||
|
"etym [[lexicography]]"
|
||||||
|
),
|
||||||
|
Some (Cmd::Etymology (EntryQuery::basic ("lexicography")))
|
||||||
|
);
|
||||||
|
assert_eq! (
|
||||||
|
Cmd::parse_str (
|
||||||
|
"ety [[lexicography]]"
|
||||||
|
),
|
||||||
|
Some (Cmd::Etymology (EntryQuery::basic ("lexicography")))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn etym_basic () {
|
||||||
|
assert_eq! (
|
||||||
|
Cmd::parse_str (
|
||||||
|
"wiktionary, can you provide the etymology of lexicography??"
|
||||||
|
),
|
||||||
|
Some (Cmd::Etymology (EntryQuery::basic ("lexicography")))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn define_basic () {
|
||||||
|
assert_eq! (
|
||||||
|
Cmd::parse_str ("wiktionary, can you define lexicography??"),
|
||||||
|
Some (Cmd::Define (EntryQuery::basic ("lexicography")))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn define_line () {
|
||||||
|
assert_eq! (
|
||||||
|
Cmd::parse_str ("define I'm twenty years old"),
|
||||||
|
Some (Cmd::Define (EntryQuery::basic ("I'm twenty years old")))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn define_wikilink () {
|
||||||
|
assert_eq! (
|
||||||
|
Cmd::parse_str ("[[hoofjob]]"),
|
||||||
|
Some (Cmd::Define (EntryQuery::basic ("hoofjob")))
|
||||||
|
);
|
||||||
|
assert_eq! (
|
||||||
|
Cmd::parse_str ("bkah blah blah [[quirkchungus]] blbalb"),
|
||||||
|
Some (Cmd::Define (EntryQuery::basic ("quirkchungus")))
|
||||||
|
);
|
||||||
|
assert_eq! (
|
||||||
|
Cmd::parse_str ("define I'm [[twenty]] years old"),
|
||||||
|
Some (Cmd::Define (EntryQuery::basic ("twenty")))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
use notify::{Event, RecursiveMode, Result, Watcher};
|
||||||
|
use std::{fs::File, io::{Read as _, Seek as _, SeekFrom, pipe}, path::{Path, PathBuf}, sync::mpsc};
|
||||||
|
|
||||||
|
pub fn default_log_file () -> PathBuf {
|
||||||
|
let base = xdg::BaseDirectories::new ()
|
||||||
|
.data_home
|
||||||
|
.expect ("could not find default log file.");
|
||||||
|
base.join ("Steam/steamapps/common/Team Fortress 2/tf/console.log")
|
||||||
|
.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));
|
||||||
|
x.unwrap_or (0)
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
};
|
||||||
|
|
||||||
|
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!??");
|
||||||
|
}
|
||||||
|
prev_size = new_size;
|
||||||
|
}
|
||||||
|
Ok (())
|
||||||
|
}
|
||||||
|
}
|
||||||
+68
@@ -0,0 +1,68 @@
|
|||||||
|
mod console;
|
||||||
|
mod command;
|
||||||
|
|
||||||
|
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 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(
|
||||||
|
name = "wiktlarp",
|
||||||
|
version,
|
||||||
|
about,
|
||||||
|
long_about,
|
||||||
|
)]
|
||||||
|
struct Args {
|
||||||
|
/// Path to TF2's console log
|
||||||
|
#[clap(
|
||||||
|
value_parser,
|
||||||
|
long,
|
||||||
|
short='f',
|
||||||
|
default_value=console::default_log_file ().into_os_string ()
|
||||||
|
)]
|
||||||
|
log_file: PathBuf,
|
||||||
|
|
||||||
|
/// RCON address to send chat commands to
|
||||||
|
#[clap(
|
||||||
|
value_parser,
|
||||||
|
long,
|
||||||
|
short='a',
|
||||||
|
default_value="127.0.0.1:27015"
|
||||||
|
)]
|
||||||
|
rcon_address: SocketAddr,
|
||||||
|
|
||||||
|
/// RCON password
|
||||||
|
#[clap(value_parser,long,short='p',default_value="monitor")]
|
||||||
|
rcon_password: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main () -> Result<()> {
|
||||||
|
let args = Args::parse ();
|
||||||
|
println! ("{:?}", args);
|
||||||
|
|
||||||
|
let mut client = <Connection<TcpStream>>::builder ()
|
||||||
|
.connect (args.rcon_address.to_string (), &args.rcon_password)
|
||||||
|
.await.unwrap ();
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok (())
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user