karma: karmaweb command + web-board link on top lists, via configurable karma_url
All checks were successful
ci / check (push) Successful in 47s
All checks were successful
ci / check (push) Successful in 47s
This commit is contained in:
parent
739fc8ad94
commit
7f6327270b
6 changed files with 92 additions and 7 deletions
|
|
@ -68,7 +68,7 @@ lists them at runtime and `help <command>` describes one.
|
||||||
|---|---|
|
|---|---|
|
||||||
| `ping` / `echo <text>` / `hello` | basics |
|
| `ping` / `echo <text>` / `hello` | basics |
|
||||||
| `roll [NdM]` | roll dice (default `1d6`) |
|
| `roll [NdM]` | roll dice (default `1d6`) |
|
||||||
| `karma [thing]` / `karmabottom` (bump with `thing++ [reason]` / `thing--`) | karma + rank + last reason, or top/bottom; milestones announced |
|
| `karma [thing]` / `karmabottom` / `karmaweb` (bump with `thing++ [reason]` / `thing--`) | karma + rank + last reason, or top/bottom (with a link to the web board when `karma_url` is set); `karmaweb` prints that link; milestones announced |
|
||||||
| `md5` / `sha1` / `sha256` / `hash <text>` | hex digests |
|
| `md5` / `sha1` / `sha256` / `hash <text>` | hex digests |
|
||||||
| `add <location>` then `w [location]` | weather (wttr.in), per-user location |
|
| `add <location>` then `w [location]` | weather (wttr.in), per-user location |
|
||||||
| `crypto <sym>` | coin price in USD (Bitstamp) |
|
| `crypto <sym>` | coin price in USD (Bitstamp) |
|
||||||
|
|
@ -124,7 +124,9 @@ RUSTBOT_CONFIG=/etc/rustbot.conf cargo run
|
||||||
Config file keys: `server`, `port`, `tls`, `nick`, `user`, `realname`,
|
Config file keys: `server`, `port`, `tls`, `nick`, `user`, `realname`,
|
||||||
`channels` (comma/space-separated), `prefix`, `verbose` (wire logging, default
|
`channels` (comma/space-separated), `prefix`, `verbose` (wire logging, default
|
||||||
on), `password`, `sasl_user`, `sasl_pass`, `name` (a network's log label), and
|
on), `password`, `sasl_user`, `sasl_pass`, `name` (a network's log label), and
|
||||||
`locale` (`en`/`es`/`fr`, default `en`). Whole-line `#`/`;` comments only —
|
`locale` (`en`/`es`/`fr`, default `en`), and `karma_url` (public web board —
|
||||||
|
enables the `karmaweb` command and a link on the `karma`/`karmabottom` top
|
||||||
|
lists; empty disables both). Whole-line `#`/`;` comments only —
|
||||||
inline comments would clash with channel `#`s.
|
inline comments would clash with channel `#`s.
|
||||||
|
|
||||||
### Locales
|
### Locales
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,11 @@ impl Bot {
|
||||||
pub fn new(config: Config, conn: Connection) -> Bot {
|
pub fn new(config: Config, conn: Connection) -> Bot {
|
||||||
let current_nick = config.nick.clone();
|
let current_nick = config.nick.clone();
|
||||||
|
|
||||||
let mods = modules::all(&config.name, crate::i18n::Locale::from_code(&config.locale));
|
let mods = modules::all(
|
||||||
|
&config.name,
|
||||||
|
crate::i18n::Locale::from_code(&config.locale),
|
||||||
|
(!config.karma_url.is_empty()).then(|| config.karma_url.clone()),
|
||||||
|
);
|
||||||
let mut routes: HashMap<&'static str, usize> = HashMap::new();
|
let mut routes: HashMap<&'static str, usize> = HashMap::new();
|
||||||
for (i, m) in mods.iter().enumerate() {
|
for (i, m) in mods.iter().enumerate() {
|
||||||
for spec in m.commands() {
|
for spec in m.commands() {
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,11 @@ const COMMANDS: &[CommandSpec] = &[
|
||||||
usage: "karmabottom",
|
usage: "karmabottom",
|
||||||
about: "the lowest-karma things",
|
about: "the lowest-karma things",
|
||||||
},
|
},
|
||||||
|
CommandSpec {
|
||||||
|
name: "karmaweb",
|
||||||
|
usage: "karmaweb",
|
||||||
|
about: "link to the web karma board",
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const MAX_THING_LEN: usize = 32;
|
const MAX_THING_LEN: usize = 32;
|
||||||
|
|
@ -31,10 +36,11 @@ const ZWSP: char = '\u{200B}';
|
||||||
pub struct Karma {
|
pub struct Karma {
|
||||||
store: Store,
|
store: Store,
|
||||||
locale: Locale,
|
locale: Locale,
|
||||||
|
url: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Karma {
|
impl Karma {
|
||||||
pub fn new(network: &str, locale: Locale) -> Karma {
|
pub fn new(network: &str, locale: Locale, url: Option<String>) -> Karma {
|
||||||
let dir = std::env::var("RUSTBOT_DATA_DIR").unwrap_or_else(|_| ".".to_string());
|
let dir = std::env::var("RUSTBOT_DATA_DIR").unwrap_or_else(|_| ".".to_string());
|
||||||
let safe: String = network
|
let safe: String = network
|
||||||
.chars()
|
.chars()
|
||||||
|
|
@ -44,6 +50,7 @@ impl Karma {
|
||||||
PathBuf::from(dir).join(format!("karma.{safe}.json")),
|
PathBuf::from(dir).join(format!("karma.{safe}.json")),
|
||||||
locale,
|
locale,
|
||||||
)
|
)
|
||||||
|
.with_url(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_path(path: PathBuf) -> Karma {
|
pub fn with_path(path: PathBuf) -> Karma {
|
||||||
|
|
@ -54,9 +61,15 @@ impl Karma {
|
||||||
Karma {
|
Karma {
|
||||||
store: Store::load(path),
|
store: Store::load(path),
|
||||||
locale,
|
locale,
|
||||||
|
url: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn with_url(mut self, url: Option<String>) -> Karma {
|
||||||
|
self.url = url;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get(&self, thing: &str) -> i64 {
|
pub fn get(&self, thing: &str) -> i64 {
|
||||||
self.store.entry(thing).map(|e| e.count).unwrap_or(0)
|
self.store.entry(thing).map(|e| e.count).unwrap_or(0)
|
||||||
}
|
}
|
||||||
|
|
@ -112,7 +125,31 @@ impl Karma {
|
||||||
(Locale::Fr, false) => "top karma",
|
(Locale::Fr, false) => "top karma",
|
||||||
(Locale::Fr, true) => "pire karma",
|
(Locale::Fr, true) => "pire karma",
|
||||||
};
|
};
|
||||||
format!("{label}: {}", items.join(", "))
|
let mut out = format!("{label}: {}", items.join(", "));
|
||||||
|
if let Some(u) = &self.url {
|
||||||
|
let more = match self.locale {
|
||||||
|
Locale::En => "full board",
|
||||||
|
Locale::Es => "tablero completo",
|
||||||
|
Locale::Fr => "tableau complet",
|
||||||
|
};
|
||||||
|
out.push_str(&format!(" · {more}: {u}"));
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
fn web_link(&self) -> String {
|
||||||
|
match &self.url {
|
||||||
|
Some(u) => match self.locale {
|
||||||
|
Locale::En => format!("karma board: {u}"),
|
||||||
|
Locale::Es => format!("tablero de karma: {u}"),
|
||||||
|
Locale::Fr => format!("tableau de karma : {u}"),
|
||||||
|
},
|
||||||
|
None => match self.locale {
|
||||||
|
Locale::En => "no web board configured".to_string(),
|
||||||
|
Locale::Es => "no hay tablero web configurado".to_string(),
|
||||||
|
Locale::Fr => "pas de tableau web configuré".to_string(),
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -132,6 +169,7 @@ impl Module for Karma {
|
||||||
None => self.leaderboard(false),
|
None => self.leaderboard(false),
|
||||||
},
|
},
|
||||||
"karmabottom" => self.leaderboard(true),
|
"karmabottom" => self.leaderboard(true),
|
||||||
|
"karmaweb" => self.web_link(),
|
||||||
_ => return vec![],
|
_ => return vec![],
|
||||||
};
|
};
|
||||||
vec![Action::reply(reply)]
|
vec![Action::reply(reply)]
|
||||||
|
|
|
||||||
|
|
@ -13,12 +13,12 @@ pub mod wiki;
|
||||||
use super::module::Module;
|
use super::module::Module;
|
||||||
use crate::i18n::Locale;
|
use crate::i18n::Locale;
|
||||||
|
|
||||||
pub fn all(network: &str, locale: Locale) -> Vec<Box<dyn Module>> {
|
pub fn all(network: &str, locale: Locale, karma_url: Option<String>) -> Vec<Box<dyn Module>> {
|
||||||
vec![
|
vec![
|
||||||
Box::new(builtins::Builtins),
|
Box::new(builtins::Builtins),
|
||||||
Box::new(dice::Dice::new()),
|
Box::new(dice::Dice::new()),
|
||||||
Box::new(hash::Hash),
|
Box::new(hash::Hash),
|
||||||
Box::new(karma::Karma::new(network, locale)),
|
Box::new(karma::Karma::new(network, locale, karma_url)),
|
||||||
Box::new(crypto::Crypto),
|
Box::new(crypto::Crypto),
|
||||||
Box::new(define::Define),
|
Box::new(define::Define),
|
||||||
Box::new(down::Down),
|
Box::new(down::Down),
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ pub struct Config {
|
||||||
pub sasl_user: Option<String>,
|
pub sasl_user: Option<String>,
|
||||||
pub sasl_pass: Option<String>,
|
pub sasl_pass: Option<String>,
|
||||||
pub locale: String,
|
pub locale: String,
|
||||||
|
pub karma_url: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Config {
|
impl Default for Config {
|
||||||
|
|
@ -38,6 +39,7 @@ impl Default for Config {
|
||||||
sasl_user: None,
|
sasl_user: None,
|
||||||
sasl_pass: None,
|
sasl_pass: None,
|
||||||
locale: "en".to_string(),
|
locale: "en".to_string(),
|
||||||
|
karma_url: String::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -163,6 +165,7 @@ fn apply_kv(c: &mut Config, key: &str, value: &str, where_: &str) {
|
||||||
"sasl_user" => c.sasl_user = (!value.is_empty()).then(|| value.to_string()),
|
"sasl_user" => c.sasl_user = (!value.is_empty()).then(|| value.to_string()),
|
||||||
"sasl_pass" => c.sasl_pass = (!value.is_empty()).then(|| value.to_string()),
|
"sasl_pass" => c.sasl_pass = (!value.is_empty()).then(|| value.to_string()),
|
||||||
"locale" | "lang" => c.locale = value.to_string(),
|
"locale" | "lang" => c.locale = value.to_string(),
|
||||||
|
"karma_url" | "web_url" => c.karma_url = value.to_string(),
|
||||||
other => log(&format!("{where_}: unknown key '{other}'")),
|
other => log(&format!("{where_}: unknown key '{other}'")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -223,6 +226,9 @@ fn apply_env(c: &mut Config) {
|
||||||
if let Ok(v) = std::env::var("IRC_LOCALE") {
|
if let Ok(v) = std::env::var("IRC_LOCALE") {
|
||||||
c.locale = v;
|
c.locale = v;
|
||||||
}
|
}
|
||||||
|
if let Ok(v) = std::env::var("IRC_KARMA_URL") {
|
||||||
|
c.karma_url = v;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_bool(value: &str) -> bool {
|
fn parse_bool(value: &str) -> bool {
|
||||||
|
|
|
||||||
|
|
@ -259,3 +259,38 @@ fn replies_in_spanish_for_es_locale() {
|
||||||
let _ = std::fs::remove_file(&path);
|
let _ = std::fs::remove_file(&path);
|
||||||
let _ = std::fs::remove_file(&log);
|
let _ = std::fs::remove_file(&log);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn karmaweb_command_and_leaderboard_suffix() {
|
||||||
|
let path = std::env::temp_dir().join("rubot-test-karma-web.json");
|
||||||
|
let log = std::env::temp_dir().join("rubot-test-karma-web.events.jsonl");
|
||||||
|
let _ = std::fs::remove_file(&path);
|
||||||
|
let _ = std::fs::remove_file(&log);
|
||||||
|
let mut k =
|
||||||
|
Karma::with_path(path.clone()).with_url(Some("https://karma.devtronic.pro/".to_string()));
|
||||||
|
// dedicated command returns the link
|
||||||
|
let w = reply_of(&mut k, "karmaweb", &[]);
|
||||||
|
assert!(w.contains("https://karma.devtronic.pro/"), "{w}");
|
||||||
|
// link is appended to the aggregate list...
|
||||||
|
k.on_message(&chat("a", "rust++ python++"));
|
||||||
|
let top = reply_of(&mut k, "karma", &[]);
|
||||||
|
assert!(top.starts_with("karma top:"), "{top}");
|
||||||
|
assert!(
|
||||||
|
top.contains("full board: https://karma.devtronic.pro/"),
|
||||||
|
"{top}"
|
||||||
|
);
|
||||||
|
// ...but not to a single-thing lookup (kept clean)
|
||||||
|
let one = reply_of(&mut k, "karma", &["rust"]);
|
||||||
|
assert!(!one.contains("http"), "{one}");
|
||||||
|
let _ = std::fs::remove_file(&path);
|
||||||
|
let _ = std::fs::remove_file(&log);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn karmaweb_without_url_is_graceful() {
|
||||||
|
let path = std::env::temp_dir().join("rubot-test-karma-nourl.json");
|
||||||
|
let _ = std::fs::remove_file(&path);
|
||||||
|
let mut k = Karma::with_path(path.clone());
|
||||||
|
assert!(reply_of(&mut k, "karmaweb", &[]).contains("no web board configured"));
|
||||||
|
let _ = std::fs::remove_file(&path);
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue