Compare commits
10
Commits
1ae6fd5f4b
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f0dc882945 | ||
|
|
72a8af7601 | ||
|
|
6eb7e98864 | ||
|
|
c4d4bc8735 | ||
|
|
05d48c0c3b | ||
|
|
a2404d294e | ||
|
|
5a6cb93bab | ||
|
|
acc1ad834e | ||
|
|
9d7946cbad | ||
|
|
2d603199a2 |
+2
-1
@@ -17,4 +17,5 @@ build/
|
|||||||
enwiktionary-latest-pages-articles.xml.bz2
|
enwiktionary-latest-pages-articles.xml.bz2
|
||||||
raw-wiktexttract-data.jsonl
|
raw-wiktexttract-data.jsonl
|
||||||
*.db
|
*.db
|
||||||
raw-wiktextract-data.jsonl
|
raw-wiktextract-data.jsonlwords.db
|
||||||
|
words.db-journal
|
||||||
|
|||||||
@@ -1,3 +1,26 @@
|
|||||||
# wiktionary-tf2
|
# wiktlarp-tf2
|
||||||
|
|
||||||
this is a really stupid script to gloss `[[wikilinked]]` words sent in tf2 chat.
|
larp as en.wiktionary.org.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
this is a really stupid script to gloss `[[wikilinked]]` words sent in
|
||||||
|
tf2 chat. it works by reading TF2's console log file for events, and
|
||||||
|
responding over RCON.
|
||||||
|
|
||||||
|
```
|
||||||
|
con_logfile console.log
|
||||||
|
ip 0.0.0.0
|
||||||
|
rcon_password monitor
|
||||||
|
net_start
|
||||||
|
```
|
||||||
|
|
||||||
|
wiktionary entries are discovered via a wiktextract jsonl dump
|
||||||
|
converted to sqlite via the rust program in `db`. it is also desirable
|
||||||
|
to create an index for the words:
|
||||||
|
|
||||||
|
```clj
|
||||||
|
(let [ds (sql/get-datasource {:dbtype "sqlite" :dbname "db/words.db"})]
|
||||||
|
(with-open [conn (sql/get-connection ds)]
|
||||||
|
(sql/execute! conn ["create index idx on words(word)"])))
|
||||||
|
```
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
raw-wiktextract-data.jsonl.gz
|
||||||
|
target/
|
||||||
|
words.db
|
||||||
|
words.db-journal
|
||||||
|
words.db-wal
|
||||||
|
words.db-shm
|
||||||
|
words.preview.db
|
||||||
|
flamegraph.svg
|
||||||
Generated
+1075
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,15 @@
|
|||||||
|
[package]
|
||||||
|
name = "db"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
clap = { version = "4.6.5", features = ["derive"] }
|
||||||
|
clio = { version = "0.3.5", features = ["clap-parse"] }
|
||||||
|
diesel = { version = "2.3.11", features = ["r2d2", "sqlite"] }
|
||||||
|
flate2 = { version = "1.1.9", features = ["zlib-rs"] }
|
||||||
|
indicatif = { version = "0.18.6", features = ["rayon"] }
|
||||||
|
rayon = "1.12.0"
|
||||||
|
serde = "1.0.229"
|
||||||
|
serde_json = "1.0.151"
|
||||||
|
wiktionary-schema = "0.3.0"
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{ rustPlatform
|
||||||
|
}:
|
||||||
|
|
||||||
|
rustPlatform.buildRustPackage (finalAttrs: {
|
||||||
|
pname = "make-db";
|
||||||
|
version = "0.1.0";
|
||||||
|
src = ./.;
|
||||||
|
cargoLock.lockFile = ./Cargo.lock;
|
||||||
|
})
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# For documentation on how to configure this file,
|
||||||
|
# see https://diesel.rs/guides/configuring-diesel-cli
|
||||||
|
|
||||||
|
[print_schema]
|
||||||
|
file = "src/schema.rs"
|
||||||
|
custom_type_derives = ["diesel::query_builder::QueryId", "Clone"]
|
||||||
|
|
||||||
|
[migrations_directory]
|
||||||
|
dir = "migrations"
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- This file should undo anything in `up.sql`
|
||||||
|
drop table words
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
-- Your SQL goes here
|
||||||
|
create table words (
|
||||||
|
id integer not null primary key autoincrement,
|
||||||
|
word varchar not null,
|
||||||
|
info text not null
|
||||||
|
)
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
use diesel::prelude::*;
|
||||||
|
use diesel::connection::SimpleConnection;
|
||||||
|
use diesel::r2d2::{ConnectionManager, CustomizeConnection, Pool};
|
||||||
|
use std::env;
|
||||||
|
use crate::models::*;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Fuck ();
|
||||||
|
|
||||||
|
impl CustomizeConnection<SqliteConnection, diesel::r2d2::Error> for Fuck {
|
||||||
|
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 (())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_release (&self, _conn: SqliteConnection) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_pool () -> Pool<ConnectionManager<SqliteConnection>> {
|
||||||
|
let url = env::var ("DATABASE_URL")
|
||||||
|
.expect ("DATABASE_URL must be set");
|
||||||
|
let manager = ConnectionManager::<SqliteConnection>::new (url);
|
||||||
|
Pool::builder ()
|
||||||
|
.connection_customizer (Box::new (Fuck ()))
|
||||||
|
.test_on_check_out (true)
|
||||||
|
.build (manager)
|
||||||
|
.expect ("Could not build connection pool")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn connect () -> SqliteConnection {
|
||||||
|
let database_url = env::var ("DATABASE_URL")
|
||||||
|
.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 ();
|
||||||
|
conn
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_word (w : &str) -> Vec<Word> {
|
||||||
|
let conn = &mut connect ();
|
||||||
|
use crate::schema::words::dsl::*;
|
||||||
|
let results = 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! ()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn entry_as_word (entry : &serde_json::Value) -> Option<&str> {
|
||||||
|
entry.get ("word")?.as_str ()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert_word (
|
||||||
|
conn : &mut SqliteConnection,
|
||||||
|
entry : serde_json::Value,
|
||||||
|
) -> Option<()> {
|
||||||
|
use crate::schema::words;
|
||||||
|
let word = entry_as_word (&entry)?;
|
||||||
|
let new_word = NewWord {
|
||||||
|
word,
|
||||||
|
info: &entry.to_string (),
|
||||||
|
};
|
||||||
|
diesel::insert_into (words::table)
|
||||||
|
.values (new_word)
|
||||||
|
.execute (conn).map_or_else (
|
||||||
|
|e| {
|
||||||
|
println! ("error for {}: {}", word, e);
|
||||||
|
None
|
||||||
|
},
|
||||||
|
Some
|
||||||
|
)?;
|
||||||
|
Some (())
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
pub mod models;
|
||||||
|
pub mod schema;
|
||||||
|
pub mod db;
|
||||||
|
|
||||||
|
use diesel::SqliteConnection;
|
||||||
|
use diesel::r2d2::{ConnectionManager, Pool, PooledConnection};
|
||||||
|
use flate2::read::GzDecoder;
|
||||||
|
use indicatif::{ProgressBar, ProgressStyle};
|
||||||
|
use indicatif::ParallelProgressIterator;
|
||||||
|
use serde_json::Value;
|
||||||
|
use wiktionary_schema::en::WordData;
|
||||||
|
use std::{fs::File, io::{self, BufRead, BufReader}, path::PathBuf, sync::RwLock};
|
||||||
|
use clap::Parser;
|
||||||
|
use rayon::prelude::*;
|
||||||
|
|
||||||
|
/// Build an SQLite database from a Wiktextract JSONLines dump.
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
#[command(name = "make-db", version, about, long_about = None)]
|
||||||
|
struct Args {
|
||||||
|
/// Input file
|
||||||
|
#[clap(long, value_parser)]
|
||||||
|
wiktextract_dump : PathBuf,
|
||||||
|
|
||||||
|
/// Output file
|
||||||
|
#[clap(long, value_parser)]
|
||||||
|
database : Option<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open_wiktextract_dump (path : PathBuf) -> Result<
|
||||||
|
BufReader<GzDecoder<File>>, io::Error
|
||||||
|
> {
|
||||||
|
Ok (BufReader::new (GzDecoder::new (File::open (path)?)))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(nonstandard_style)]
|
||||||
|
const wiktextract_dump_lines : u64 = 10736399;
|
||||||
|
|
||||||
|
fn do_line (
|
||||||
|
conn: &mut PooledConnection<ConnectionManager<SqliteConnection>>,
|
||||||
|
retries : &RwLock<Vec<String>>,
|
||||||
|
line : String
|
||||||
|
) {
|
||||||
|
if let Ok (word) = serde_json::from_str::<Value> (&line) {
|
||||||
|
if let None = db::insert_word (conn, word) {
|
||||||
|
retries.write ().unwrap ().push (line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<(), String> {
|
||||||
|
let args = Args::parse ();
|
||||||
|
let d = open_wiktextract_dump (args.wiktextract_dump).unwrap ();
|
||||||
|
let bar = ProgressBar::new (wiktextract_dump_lines);
|
||||||
|
bar.set_style (
|
||||||
|
ProgressStyle::with_template(
|
||||||
|
"{elapsed} / ETA {eta} / rate {per_sec} {bar:40.cyan/blue} {pos:>7}/{len:7} {msg}"
|
||||||
|
) .unwrap()
|
||||||
|
);
|
||||||
|
let lines = d.lines ().map (|l| l.unwrap ().to_owned ())
|
||||||
|
.par_bridge ()
|
||||||
|
.progress_with (bar);
|
||||||
|
let pool = db::get_pool ();
|
||||||
|
let retries = RwLock::new (vec! []);
|
||||||
|
|
||||||
|
lines.for_each (|line| {
|
||||||
|
let conn = &mut pool.clone ().get ().unwrap ();
|
||||||
|
do_line (conn, &retries, line)
|
||||||
|
});
|
||||||
|
|
||||||
|
println! ("do retries now");
|
||||||
|
let conn = &mut pool.clone ().get ().unwrap ();
|
||||||
|
for line in retries.read ().unwrap ().iter () {
|
||||||
|
println! ("retrying");
|
||||||
|
if let Ok (word) = serde_json::from_str::<Value> (&line) {
|
||||||
|
db::insert_word (conn, word);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok (())
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
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,
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
// @generated automatically by Diesel CLI.
|
||||||
|
|
||||||
|
diesel::table! {
|
||||||
|
words (id) {
|
||||||
|
id -> Integer,
|
||||||
|
word -> Text,
|
||||||
|
info -> Text,
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-2
@@ -19,9 +19,9 @@ let
|
|||||||
# sqlite3
|
# sqlite3
|
||||||
# ];
|
# ];
|
||||||
in mkCljBin' {
|
in mkCljBin' {
|
||||||
name = "wiktionary-tf2";
|
name = "wiktlarp";
|
||||||
version = "0.1.0";
|
version = "0.1.0";
|
||||||
projectSrc = lib.cleanSource ./.;
|
projectSrc = lib.cleanSource ./.;
|
||||||
lockfile = ./deps-lock.json;
|
lockfile = ./deps-lock.json;
|
||||||
main-ns = "wiktionary-tf2.main";
|
main-ns = "wiktlarp.main";
|
||||||
}
|
}
|
||||||
|
|||||||
+25
-13
@@ -1,14 +1,6 @@
|
|||||||
{
|
{
|
||||||
"lock-version": 4,
|
"lock-version": 4,
|
||||||
"git-deps": [
|
"git-deps": [],
|
||||||
{
|
|
||||||
"lib": "io.github.anteoas/hawkeye",
|
|
||||||
"url": "https://github.com/anteoas/hawkeye.git",
|
|
||||||
"rev": "5b3f5a99e9edfe482174258a864b577f7a4eeb47",
|
|
||||||
"git-dir": "https/github.com/anteoas/hawkeye",
|
|
||||||
"hash": "sha256-ghKXjf4e3U8rlWCpffPRHqvf367RBs+VnSpRF6tI0xk="
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"mvn-deps": [
|
"mvn-deps": [
|
||||||
{
|
{
|
||||||
"mvn-path": "aleph/aleph/0.4.1/aleph-0.4.1.jar",
|
"mvn-path": "aleph/aleph/0.4.1/aleph-0.4.1.jar",
|
||||||
@@ -190,6 +182,16 @@
|
|||||||
"mvn-repo": "https://repo.clojars.org/",
|
"mvn-repo": "https://repo.clojars.org/",
|
||||||
"hash": "sha256-q4PzoWHUY53W2TZWihPpw+qXB4QWWVnS1iW3WlvIxFg="
|
"hash": "sha256-q4PzoWHUY53W2TZWihPpw+qXB4QWWVnS1iW3WlvIxFg="
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"mvn-path": "integrant/integrant/1.0.1/integrant-1.0.1.jar",
|
||||||
|
"mvn-repo": "https://repo.clojars.org/",
|
||||||
|
"hash": "sha256-dHxoCJNqPEcXakMdx6T89cQKH7ZQf9LmZtMO4fzr7f0="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mvn-path": "integrant/integrant/1.0.1/integrant-1.0.1.pom",
|
||||||
|
"mvn-repo": "https://repo.clojars.org/",
|
||||||
|
"hash": "sha256-zaEWby3a9q8gwevhNdchEwERgzbUG9NgqSBQ83C3Wdo="
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"mvn-path": "io/aleph/dirigiste/0.1.3/dirigiste-0.1.3.jar",
|
"mvn-path": "io/aleph/dirigiste/0.1.3/dirigiste-0.1.3.jar",
|
||||||
"mvn-repo": "https://repo1.maven.org/maven2/",
|
"mvn-repo": "https://repo1.maven.org/maven2/",
|
||||||
@@ -236,14 +238,14 @@
|
|||||||
"hash": "sha256-k7OxHaltUXiIDfjFBT8Yz8eByv8Nnd9LPGRyRKnRws8="
|
"hash": "sha256-k7OxHaltUXiIDfjFBT8Yz8eByv8Nnd9LPGRyRKnRws8="
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"mvn-path": "net/java/dev/jna/jna/5.14.0/jna-5.14.0.jar",
|
"mvn-path": "net/java/dev/jna/jna/3.2.2/jna-3.2.2.jar",
|
||||||
"mvn-repo": "https://repo1.maven.org/maven2/",
|
"mvn-repo": "https://repo1.maven.org/maven2/",
|
||||||
"hash": "sha256-NO0eHyf6iWvKUNvE6ZzzcylnzsOHp6DV40hsCWc/6MY="
|
"hash": "sha256-/o8Xb7+sDBzy+M19M2EPp8VI9VFy0C0gLjJwF02p2HU="
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"mvn-path": "net/java/dev/jna/jna/5.14.0/jna-5.14.0.pom",
|
"mvn-path": "net/java/dev/jna/jna/3.2.2/jna-3.2.2.pom",
|
||||||
"mvn-repo": "https://repo1.maven.org/maven2/",
|
"mvn-repo": "https://repo1.maven.org/maven2/",
|
||||||
"hash": "sha256-4E4llRUB3yWtx7Hc22xTNzyUiXuE0+FJISknY+4Hrj0="
|
"hash": "sha256-tIAbOJ/rE2ZJaYehnjKRtxAw2BnAmYRvjkF/ayD6xJQ="
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"mvn-path": "org/clojure/clojure/1.10.3/clojure-1.10.3.jar",
|
"mvn-path": "org/clojure/clojure/1.10.3/clojure-1.10.3.jar",
|
||||||
@@ -564,6 +566,16 @@
|
|||||||
"mvn-path": "tigris/tigris/0.1.2/tigris-0.1.2.pom",
|
"mvn-path": "tigris/tigris/0.1.2/tigris-0.1.2.pom",
|
||||||
"mvn-repo": "https://repo.clojars.org/",
|
"mvn-repo": "https://repo.clojars.org/",
|
||||||
"hash": "sha256-H9VZA1l1INzUrnbmoz7/XjWmFUIrutKo7ZrDMqr75KA="
|
"hash": "sha256-H9VZA1l1INzUrnbmoz7/XjWmFUIrutKo7ZrDMqr75KA="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mvn-path": "weavejester/dependency/1.0.0/dependency-1.0.0.jar",
|
||||||
|
"mvn-repo": "https://repo.clojars.org/",
|
||||||
|
"hash": "sha256-XxpuyFNiyj3VVUj25hocVNmbLc0vv9/EdIBK5m0TbpI="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mvn-path": "weavejester/dependency/1.0.0/dependency-1.0.0.pom",
|
||||||
|
"mvn-repo": "https://repo.clojars.org/",
|
||||||
|
"hash": "sha256-5NIafyzna3LBsVI8OHFubwOWmDYklwz18sPRfLp9WzY="
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,5 @@
|
|||||||
clj-rcon/clj-rcon {:mvn/version "0.1.1"}
|
clj-rcon/clj-rcon {:mvn/version "0.1.1"}
|
||||||
hawk/hawk {:mvn/version "0.2.11"}
|
hawk/hawk {:mvn/version "0.2.11"}
|
||||||
ch.qos.logback/logback-classic #:mvn{:version "1.1.3"}
|
ch.qos.logback/logback-classic #:mvn{:version "1.1.3"}
|
||||||
io.github.anteoas/hawkeye
|
integrant/integrant {:mvn/version "1.0.1"}}
|
||||||
{:git/sha "5b3f5a99e9edfe482174258a864b577f7a4eeb47"}}
|
|
||||||
:paths ["src" "resources"]}
|
:paths ["src" "resources"]}
|
||||||
|
|||||||
Generated
+22
-1
@@ -128,7 +128,28 @@
|
|||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"clj-nix": "clj-nix",
|
"clj-nix": "clj-nix",
|
||||||
"nixpkgs": "nixpkgs_2"
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,6 +2,10 @@
|
|||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||||
clj-nix.url = "github:jlesquembre/clj-nix";
|
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, clj-nix, ... }@inputs:
|
||||||
@@ -29,18 +33,25 @@
|
|||||||
|
|
||||||
packages = each-system ({ pkgs, ... }: {
|
packages = each-system ({ pkgs, ... }: {
|
||||||
default = pkgs.callPackage ./default.nix {};
|
default = pkgs.callPackage ./default.nix {};
|
||||||
|
db = pkgs.callPackage ./db {};
|
||||||
});
|
});
|
||||||
|
|
||||||
devShells = each-system ({ pkgs, system, ... }: {
|
devShells = each-system ({ pkgs, system, ... }: {
|
||||||
default = pkgs.mkShell {
|
default = pkgs.mkShell {
|
||||||
|
inputsFrom = [ self.packages.${system}.db ];
|
||||||
|
DATABASE_URL = "words.db";
|
||||||
packages = with pkgs; [
|
packages = with pkgs; [
|
||||||
zprint
|
zprint
|
||||||
clojure
|
clojure
|
||||||
babashka
|
babashka
|
||||||
python3
|
python3
|
||||||
|
(sqlite.override { interactive = true; })
|
||||||
python314Packages.wiktextract
|
python314Packages.wiktextract
|
||||||
sqlite-web
|
sqlite-web
|
||||||
jq
|
jq
|
||||||
|
rust-analyzer
|
||||||
|
diesel-cli
|
||||||
|
cargo-flamegraph
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,109 +0,0 @@
|
|||||||
(ns wiktionary-tf2.main
|
|
||||||
(:require [clojure.java.io :as io]
|
|
||||||
[babashka.fs :as fs]
|
|
||||||
[babashka.process :as p]
|
|
||||||
[clj-rcon.core :as rcon]
|
|
||||||
[clojure.tools.logging :as l]
|
|
||||||
[clojure.string :as str]
|
|
||||||
[hawkeye.core :as hawk])
|
|
||||||
(:gen-class))
|
|
||||||
|
|
||||||
(def log-file
|
|
||||||
(-> "~/.local/share/Steam/steamapps/common/Team Fortress 2/tf/console.log"
|
|
||||||
fs/expand-home
|
|
||||||
fs/file))
|
|
||||||
|
|
||||||
(def words-file "words.gz")
|
|
||||||
|
|
||||||
(def ^:dynamic *rcon*)
|
|
||||||
|
|
||||||
(defn lookup-word [word]
|
|
||||||
(->> (p/shell {:out :string}
|
|
||||||
"zgrep"
|
|
||||||
(format "^%s\t" word)
|
|
||||||
words-file)
|
|
||||||
:out
|
|
||||||
str/split-lines
|
|
||||||
(map #(nth (re-matches #"([^\t]*)\t(.*)\n?" %)
|
|
||||||
2 nil))
|
|
||||||
(filter #(not (empty? %))) ; remove nil and ""
|
|
||||||
first))
|
|
||||||
|
|
||||||
(defn parse-chat-message [x]
|
|
||||||
(when-let [[_ _dead? author body]
|
|
||||||
(re-matches #"(\*사망\* )?(.*?) : (.*)" x)]
|
|
||||||
{:author author :body body}))
|
|
||||||
|
|
||||||
(defn say-word [word]
|
|
||||||
(->> (format "%s: %s" word (or (lookup-word word)
|
|
||||||
"no entry found }:("))
|
|
||||||
(take 128)
|
|
||||||
(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)
|
|
||||||
(say-word w)))
|
|
||||||
|
|
||||||
(defn rcon-connect [host port password]
|
|
||||||
(l/info "attempting rcon connection...")
|
|
||||||
(or (try (let [c @(rcon/connect host port password)]
|
|
||||||
@(rcon/exec c "echo \"WIKTIONARY 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)))
|
|
||||||
|
|
||||||
(defonce prev-size
|
|
||||||
(atom (if (fs/exists? log-file)
|
|
||||||
(-> log-file slurp count)
|
|
||||||
0)))
|
|
||||||
|
|
||||||
(defn handler []
|
|
||||||
(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)))))
|
|
||||||
|
|
||||||
(defn start-watcher! []
|
|
||||||
(hawk/watch (str (fs/parent log-file))
|
|
||||||
(bound-fn* (fn [ev]
|
|
||||||
(when (= (str (fs/file-name log-file))
|
|
||||||
(:file ev))
|
|
||||||
(handler))))
|
|
||||||
(fn [e ctx]
|
|
||||||
(l/error e "error in watcher"))))
|
|
||||||
|
|
||||||
(defn start-logger! []
|
|
||||||
@(rcon/exec *rcon* (format "con_logfile %s"
|
|
||||||
(fs/file-name log-file)))
|
|
||||||
@(rcon/exec *rcon* "echo \"wiktionary logging now\"")
|
|
||||||
(Thread/sleep 400)
|
|
||||||
(l/info "logfile has been set up!"))
|
|
||||||
|
|
||||||
(defn -main []
|
|
||||||
(binding [*rcon* (rcon-connect "127.0.0.1" 27015 "monitor")]
|
|
||||||
(start-logger!)
|
|
||||||
(start-watcher!)
|
|
||||||
(read-line)))
|
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
(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")))))))
|
||||||
Reference in New Issue
Block a user