commit a70481d1ff12201f0930f84b59282969fc5aa312
parent 9c2cd5f53f3797ebf88596384101de1f989b6637
Author: tomvig38@gmail.com <tomvig38@gmail.com>
Date: Mon, 25 Oct 2021 07:39:47 +0000
Aldonue statistikoj
Diffstat:
M | .gitignore | | | 1 | + |
M | Cargo.lock | | | 118 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
M | Cargo.toml | | | 1 | + |
M | src/main.rs | | | 172 | ++++++++++++++++++++++++++++++++++++++++++++----------------------------------- |
A | src/requests.rs | | | 88 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
5 files changed, 304 insertions(+), 76 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -1 +1,2 @@
/target
+vorto.bincode
diff --git a/Cargo.lock b/Cargo.lock
@@ -3,6 +3,36 @@
version = 3
[[package]]
+name = "async-mutex"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "479db852db25d9dbf6204e6cb6253698f175c15726470f78af0d918e99d6156e"
+dependencies = [
+ "event-listener",
+]
+
+[[package]]
+name = "async-rwlock"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "261803dcc39ba9e72760ba6e16d0199b1eef9fc44e81bffabbebb9f5aea3906c"
+dependencies = [
+ "async-mutex",
+ "event-listener",
+]
+
+[[package]]
+name = "async-trait"
+version = "0.1.51"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44318e776df68115a881de9a8fd1b9e53368d7a4a5ce4cc48517da3393233a5e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -53,6 +83,40 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"
[[package]]
+name = "cached"
+version = "0.26.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2bc2fd249a24a9cdd4276f3a3e0461713271ab63b0e9e656e200e8e21c8c927"
+dependencies = [
+ "async-mutex",
+ "async-rwlock",
+ "async-trait",
+ "cached_proc_macro",
+ "cached_proc_macro_types",
+ "futures",
+ "hashbrown",
+ "once_cell",
+]
+
+[[package]]
+name = "cached_proc_macro"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3531903b39df48a378a7ed515baee7c1fff32488489c7d0725eb1749b22a91"
+dependencies = [
+ "cached_proc_macro_types",
+ "darling",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "cached_proc_macro_types"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a4f925191b4367301851c6d99b09890311d74b0d43f274c0b34c86d308a3663"
+
+[[package]]
name = "cc"
version = "1.0.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -89,6 +153,41 @@ dependencies = [
]
[[package]]
+name = "darling"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "757c0ded2af11d8e739c4daea1ac623dd1624b06c844cf3f5a39f1bdbd99bb12"
+dependencies = [
+ "darling_core",
+ "darling_macro",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c34d8efb62d0c2d7f60ece80f75e5c63c1588ba68032740494b0b9a996466e3"
+dependencies = [
+ "fnv",
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim",
+ "syn",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ade7bff147130fe5e6d39f089c6bd49ec0250f35d70b2eebf72afdfc919f15cc"
+dependencies = [
+ "darling_core",
+ "quote",
+ "syn",
+]
+
+[[package]]
name = "encoding"
version = "0.2.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -162,6 +261,12 @@ dependencies = [
]
[[package]]
+name = "event-listener"
+version = "2.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7531096570974c3a9dcf9e4b8e1cede1ec26cf5046219fb3b9d897503b9be59"
+
+[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -379,6 +484,12 @@ dependencies = [
]
[[package]]
+name = "ident_case"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
+
+[[package]]
name = "idna"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -865,6 +976,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]]
+name = "strsim"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
+
+[[package]]
name = "syn"
version = "1.0.80"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1063,6 +1180,7 @@ name = "vortaroboto"
version = "0.1.0"
dependencies = [
"bincode",
+ "cached",
"futures",
"irc",
"log",
diff --git a/Cargo.toml b/Cargo.toml
@@ -16,3 +16,4 @@ log = "0.4"
simple_logger = "1.13.0"
bincode = "1.3.3"
snap = "1.0.5"
+cached = "0.26.2"
diff --git a/src/main.rs b/src/main.rs
@@ -1,6 +1,6 @@
use futures::prelude::*;
use irc::client::prelude::*;
-use serde::Deserialize;
+use serde::{de::DeserializeOwned, ser::Serialize};
use std::{collections::HashMap, str::FromStr};
use log::{debug, info};
@@ -10,56 +10,28 @@ use simple_logger::SimpleLogger;
mod colors;
use colors::{ColorKind, Formatted, FormattingKind};
+mod requests;
+use requests::{Difino, Traduko, Trovo, Vorto, Vortfarado, difinu, trovu};
+
const NICK: &'static str = "vortaroboto";
-const TROVI_URL: &'static str = "http://www.simplavortaro.org/api/v1/trovi";
-const VORTO_URL: &'static str = "http://www.simplavortaro.org/api/v1/vorto";
const LINEO_SEP: &'static str = "\r\n";
-#[derive(Deserialize, Debug)]
-struct Vortpartoj {
- vorto: Option<String>,
- parto: String,
-}
-
-#[derive(Deserialize, Debug)]
-struct Vortfarado {
- partoj: Vec<Vortpartoj>,
- rezulto: String,
-}
-
-#[derive(Deserialize, Debug)]
-struct Traduko {
- kodo: String,
- vorto: Option<String>,
- lingvo: String,
- traduko: String,
-}
-
-#[derive(Deserialize, Debug)]
-struct Trovo {
- malpreciza: Vec<String>,
- vortfarado: Vec<Vortfarado>,
- preciza: Vec<String>,
- tradukoj: Vec<Traduko>,
-}
-
-#[derive(Deserialize, Debug)]
-struct Ekzemplo {
- ekzemplo: String,
- fonto: Option<String>,
+fn unpack_data<T>(fname: &str) -> Result<T, String>
+where
+ T: DeserializeOwned,
+{
+ let r = snap::read::FrameDecoder::new(std::fs::File::open(fname).map_err(|e| e.to_string())?);
+ bincode::deserialize_from(r).map_err(|e| e.to_string())
}
-#[derive(Deserialize, Debug)]
-struct Difino {
- difino: String,
- ekzemploj: Vec<Ekzemplo>,
- tradukoj: Vec<Traduko>,
-}
-
-#[derive(Deserialize, Debug)]
-struct Vorto {
- difinoj: Vec<Difino>,
- vorto: String,
+fn pack_data<T>(fname: &str, data: &T) -> Result<(), String>
+where
+ T: Serialize,
+{
+ let f = std::fs::File::create(fname).map_err(|e| e.to_string())?;
+ let w = snap::write::FrameEncoder::new(f);
+ bincode::serialize_into(w, data).map_err(|e| e.to_string())?;
+ Ok(())
}
enum NumerSelektilo {
@@ -84,20 +56,6 @@ impl Default for NumerSelektilo {
}
}
-async fn trovu(vorto: &str) -> Result<Vorto, String> {
- reqwest::get(format!("{}/{}", VORTO_URL, vorto))
- .and_then(|j| j.json::<Vorto>())
- .map_err(|e| e.to_string())
- .await
-}
-
-async fn difinu(vorto: &str) -> Result<Trovo, String> {
- reqwest::get(format!("{}/{}", TROVI_URL, vorto))
- .and_then(|j| j.json::<Trovo>())
- .map_err(|e| e.to_string())
- .await
-}
-
#[tokio::main]
async fn main() -> irc::error::Result<()> {
SimpleLogger::new()
@@ -140,11 +98,28 @@ async fn main() -> irc::error::Result<()> {
};
sender.send_privmsg(target, r)?;
}
- } else if msg.contains(client.current_nickname()) {
- sender.send_privmsg(target, format!(
- "Saluton ! Mi estas {} la roboto kiun vi povas demandi pri vortoj kaj tradukoj ! Tajpu !helpu por pli'sciiĝi !",
- irc_fmt!(FormattingKind::Bold => client.current_nickname())
- ))?;
+ continue;
+ }
+
+ tokio::task::spawn(add_words(msg.to_string()));
+
+ // Donu bonaj respondoj ofte
+ if msg.contains(client.current_nickname()) {
+ let msg = match msg.split_ascii_whitespace().next() {
+ Some("saluton" | "Saluton") => {
+ format!(
+ "Saluton ! Mi estas {} la roboto kiun vi povas demandi pri vortoj kaj tradukoj ! Tajpu !helpu por pli'sciiĝi !",
+ irc_fmt!(FormattingKind::Bold => client.current_nickname())
+ )
+ }
+ Some("dankon" | "Dankon") => {
+ format!("Nedankinde {} !", message.source_nickname().unwrap_or("vi"))
+ }
+ _ => {
+ continue;
+ }
+ };
+ sender.send_privmsg(target, msg)?;
}
}
_ => (),
@@ -154,6 +129,50 @@ async fn main() -> irc::error::Result<()> {
Ok(())
}
+const VORTO_PATH: &'static str = "vorto.bincode";
+async fn add_words(frazo: String) {
+ let mut h: HashMap<String, usize> = unpack_data(VORTO_PATH).unwrap_or_default();
+
+ for vorto in frazo.split_ascii_whitespace() {
+ if let Ok(tro) = difinu(vorto.to_owned()).await {
+ if let Some(vf) = tro
+ .vortfarado
+ .iter()
+ .filter(|vf| vf.partoj.len() > 1)
+ .next()
+ {
+ // Konstrui la vera vorto
+ let mut vorto = String::with_capacity(vorto.len());
+ for p in vf.partoj.iter().take_while(|p| p.vorto.is_some()) {
+ vorto.push_str(p.parto.as_str());
+ }
+ vorto.push('-');
+
+ if let Some(v) = h.get_mut(&vorto) {
+ *v += 1;
+ } else {
+ h.insert(vorto.to_owned(), 1);
+ }
+ }
+ }
+ }
+
+ pack_data(VORTO_PATH, &h).unwrap(); // Tre neebla, sed...
+}
+
+fn stats() -> Result<String, String> {
+ let mut h: HashMap<String, usize> = unpack_data(VORTO_PATH)?;
+ let mut h: Vec<(String, usize)> = h.drain().collect();
+ h.sort_by_cached_key(|(_, s)| *s);
+ h.reverse();
+
+ Ok(h.iter()
+ .take(10)
+ .map(|(v, o)| format!("{}: {}", v, o))
+ .collect::<Vec<String>>()
+ .join(", "))
+}
+
macro_rules! parse_or_default {
($t:ty, $arg:ident) => {
if let Some(s) = $arg.next() {
@@ -207,6 +226,7 @@ async fn handle_command(cmd: &str) -> Option<String> {
Some(String::from("Uzo: etimologio {vorto}"))
}
}
+ Some("satistikoj" | "stat") => stats().ok(),
Some(u) => Some(format!("Mi ne scias kiel respondi al: {}", u)),
None => None,
}
@@ -233,10 +253,13 @@ fn helpu() -> String {
&["numero"],
"doni la vortfarado [numero] por {vorto}",
),
- ("etimolodio",
- &["vorto"],
- &[],
- "Doni la etimolodio de {vorto}")
+ (
+ "etimologio",
+ &["vorto"],
+ &[],
+ "Doni la etimologio de {vorto}",
+ ),
+ ("statistiko", &[], &[], "Donu la 10 vortoj plej uzataj.."),
];
fn format_args(am: &[&str], left: char, right: char, c: ColorKind) -> String {
@@ -295,10 +318,7 @@ fn helpu() -> String {
}
async fn etimologio(vorto: &str) -> Result<String, String> {
- let r = snap::read::FrameDecoder::new(
- std::fs::File::open("etim.bincode").map_err(|e| e.to_string())?,
- );
- let h: HashMap<String, String> = bincode::deserialize_from(r).map_err(|e| e.to_string())?;
+ let h: HashMap<String, String> = unpack_data("etim.bincode")?;
Ok(h.get(vorto)
.map(|e| e.to_owned())
@@ -306,7 +326,7 @@ async fn etimologio(vorto: &str) -> Result<String, String> {
}
async fn define_word(vorto: &str, difino: NumerSelektilo) -> Result<String, String> {
- let res: Vorto = trovu(vorto).await?;
+ let res: Vorto = trovu(vorto.to_owned()).await?;
fn format_difino(d: &Difino, index: usize, len: usize, vorto: &str, por: bool) -> String {
if por {
@@ -355,7 +375,7 @@ async fn define_word(vorto: &str, difino: NumerSelektilo) -> Result<String, Stri
}
async fn traduki(vorto: &str, fonto: Option<&str>) -> Result<String, String> {
- let res: Trovo = difinu(vorto).await?;
+ let res: Trovo = difinu(vorto.to_owned()).await?;
let tradukoj = res.tradukoj.iter();
let tradukoj: Vec<String> = if let Some(f) = fonto {
@@ -392,7 +412,7 @@ async fn traduki(vorto: &str, fonto: Option<&str>) -> Result<String, String> {
}
async fn vortfarado(vorto: &str, index: NumerSelektilo) -> Result<String, String> {
- let res: Trovo = difinu(vorto).await?;
+ let res: Trovo = difinu(vorto.to_owned()).await?;
let vf = res.vortfarado;
diff --git a/src/requests.rs b/src/requests.rs
@@ -0,0 +1,88 @@
+use futures::prelude::*;
+use serde::{Deserialize, de::DeserializeOwned};
+use cached::proc_macro::cached;
+
+const TROVI_URL: &'static str = "http://www.simplavortaro.org/api/v1/trovi";
+const VORTO_URL: &'static str = "http://www.simplavortaro.org/api/v1/vorto";
+
+#[derive(Deserialize, Debug, Clone)]
+pub struct Vortpartoj {
+ pub vorto: Option<String>,
+ pub parto: String,
+}
+
+#[derive(Deserialize, Debug, Clone)]
+pub struct Vortfarado {
+ pub partoj: Vec<Vortpartoj>,
+ pub rezulto: String,
+}
+
+#[derive(Deserialize, Debug, Clone)]
+pub struct Traduko {
+ pub kodo: String,
+ pub vorto: Option<String>,
+ pub lingvo: String,
+ pub traduko: String,
+}
+
+#[derive(Deserialize, Debug, Clone)]
+pub struct Trovo {
+ pub malpreciza: Vec<String>,
+ pub vortfarado: Vec<Vortfarado>,
+ pub preciza: Vec<String>,
+ pub tradukoj: Vec<Traduko>,
+}
+
+#[derive(Deserialize, Debug, Clone)]
+pub struct Ekzemplo {
+ pub ekzemplo: String,
+ pub fonto: Option<String>,
+}
+
+#[derive(Deserialize, Debug, Clone)]
+pub struct Difino {
+ pub difino: String,
+ pub ekzemploj: Vec<Ekzemplo>,
+ pub tradukoj: Vec<Traduko>,
+}
+
+#[derive(Deserialize, Debug, Clone)]
+pub struct Vorto {
+ pub difinoj: Vec<Difino>,
+ pub vorto: String,
+}
+
+
+async fn req<T>(url: &str, vorto: String) -> Result<T, String>
+where
+ T: DeserializeOwned,
+{
+ reqwest::get(format!("{}/{}", url, vorto.to_lowercase()))
+ .and_then(|j| j.json::<T>())
+ .map_err(|e| e.to_string())
+ .await
+}
+
+fn prepare_word(vorto: String) -> String {
+ vorto
+ .to_lowercase()
+ .chars()
+ .filter(|c| c.is_alphabetic())
+ .collect()
+}
+
+macro_rules! q {
+ ($finternal:ident, $fname:ident, $t:ty, $url:expr) => {
+ #[cached]
+ async fn $finternal(vorto: String) -> Result<$t, String> {
+ req::<$t>($url, vorto).await
+ }
+
+ pub async fn $fname(vorto: String) -> Result<$t, String> {
+ $finternal(prepare_word(vorto)).await
+ }
+ };
+}
+
+q! { t_int, trovu, Vorto, VORTO_URL }
+q! { d_int, difinu, Trovo, TROVI_URL }