Compare commits

...

2 Commits

Author SHA1 Message Date
452bd92eb1 Optimize several parts 2025-04-13 02:57:24 +10:00
55cbe57728 Lockfile maintenance 2025-04-13 02:00:21 +10:00
5 changed files with 187 additions and 74 deletions

44
Cargo.lock generated

@ -215,9 +215,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.2.18"
version = "1.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "525046617d8376e3db1deffb079e91cef90a89fc3ca5c185bbf8c9ecdd15cd5c"
checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362"
dependencies = [
"shlex",
]
@ -291,9 +291,9 @@ dependencies = [
[[package]]
name = "crossbeam-channel"
version = "0.5.14"
version = "0.5.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471"
checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2"
dependencies = [
"crossbeam-utils",
]
@ -379,9 +379,9 @@ dependencies = [
[[package]]
name = "data-encoding"
version = "2.8.0"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "575f75dfd25738df5b91b8e43e14d44bda14637a58fae779fd2b064f8bf3e010"
checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476"
[[package]]
name = "deranged"
@ -834,7 +834,7 @@ dependencies = [
"http 1.3.1",
"hyper 1.6.0",
"hyper-util",
"rustls 0.23.25",
"rustls 0.23.26",
"rustls-pki-types",
"tokio",
"tokio-rustls 0.26.2",
@ -1080,7 +1080,7 @@ dependencies = [
[[package]]
name = "kon"
version = "0.6.10"
version = "0.6.11"
dependencies = [
"kon_cmds",
"kon_libs",
@ -1091,7 +1091,7 @@ dependencies = [
[[package]]
name = "kon_cmds"
version = "0.1.7"
version = "0.1.8"
dependencies = [
"dashmap 6.1.0",
"futures",
@ -1148,9 +1148,9 @@ checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6"
[[package]]
name = "linux-raw-sys"
version = "0.9.3"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413"
checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
[[package]]
name = "litemap"
@ -1213,9 +1213,9 @@ dependencies = [
[[package]]
name = "miniz_oxide"
version = "0.8.7"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff70ce3e48ae43fa075863cef62e8b43b71a4f2382229920e0df362592919430"
checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a"
dependencies = [
"adler2",
]
@ -1349,9 +1349,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
[[package]]
name = "openssl-src"
version = "300.4.2+3.4.1"
version = "300.5.0+3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "168ce4e058f975fe43e89d9ccf78ca668601887ae736090aacc23ae353c298e2"
checksum = "e8ce546f549326b0e6052b649198487d91320875da901e7bd11a06d1ee3f9c2f"
dependencies = [
"cc",
]
@ -1527,9 +1527,9 @@ dependencies = [
[[package]]
name = "redis"
version = "0.29.3"
version = "0.29.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ccbf02626b0bfd9c0b34837c0e2d3305ad6d20c5a816bc25d622b971dae88a6"
checksum = "1bc42f3a12fd4408ce64d8efef67048a924e543bd35c6591c0447fda9054695f"
dependencies = [
"arc-swap",
"bytes",
@ -1733,9 +1733,9 @@ dependencies = [
[[package]]
name = "rustls"
version = "0.23.25"
version = "0.23.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "822ee9188ac4ec04a2f0531e55d035fb2de73f18b41a63c70c2712503b6fb13c"
checksum = "df51b5869f3a441595eac5e8ff14d486ff285f7b8c0df8770e49c3b56351f0f0"
dependencies = [
"once_cell",
"rustls-pki-types",
@ -2328,7 +2328,7 @@ version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b"
dependencies = [
"rustls 0.23.25",
"rustls 0.23.26",
"tokio",
]
@ -3125,9 +3125,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
[[package]]
name = "winnow"
version = "0.7.4"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e97b544156e9bebe1a0ffbc03484fc1ffe3100cbce3ffb17eac35f7cdd7ab36"
checksum = "63d3fcd9bba44b03821e7d699eeee959f3126dcc4aa8e4ae18ec617c2a5cea10"
dependencies = [
"memchr",
]

@ -1,6 +1,6 @@
[package]
name = "kon"
version = "0.6.10"
version = "0.6.11"
edition = "2024"
[workspace]

@ -1,6 +1,6 @@
[package]
name = "kon_cmds"
version = "0.1.7"
version = "0.1.8"
edition = "2024"
[dependencies]

@ -7,13 +7,23 @@ use {
serde::{
Deserialize,
Serialize
},
std::{
collections::HashMap,
sync::{
LazyLock,
RwLock
}
}
};
static REQWEST_: LazyLock<reqwest::Client> = LazyLock::new(reqwest::Client::new);
static LOCALE_CACHE: LazyLock<RwLock<HashMap<String, String>>> = LazyLock::new(|| RwLock::new(HashMap::new()));
#[derive(Serialize)]
struct DeepLRequest {
text: Vec<String>,
target_lang: String
target_lang: &'static str
}
#[derive(Deserialize)]
@ -21,12 +31,39 @@ struct DeepLResponse {
translations: Vec<Translation>
}
#[derive(Deserialize)]
struct DeepLLanguage {
language: String,
name: String
}
#[derive(Deserialize)]
struct DeepLUsage {
character_count: u64,
character_limit: u64
}
#[derive(Deserialize)]
struct Translation {
text: String,
detected_source_language: String
}
fn prettify_nums(num: u64) -> String {
let mut s = String::new();
let num_str = num.to_string();
let len = num_str.len();
for (i, c) in num_str.chars().enumerate() {
s.push(c);
if (len - i - 1) % 3 == 0 && i < len - 1 {
s.push(',');
}
}
s
}
/// Translate a given message using DeepL
#[poise::command(
context_menu_command = "Translate via DeepL",
@ -55,6 +92,10 @@ pub async fn translate(
return Ok(());
}
if LOCALE_CACHE.read().unwrap().is_empty() {
update_locale_cache(&deepl_key).await.expect("Failed to update locale cache...");
}
ctx.defer().await?;
let api_url = if deepl_key.ends_with(":fx") {
@ -63,15 +104,14 @@ pub async fn translate(
"https://api.deepl.com"
};
let client = reqwest::Client::new();
let resp = match client
let resp = match REQWEST_
.post(format!("{api_url}/v2/translate"))
.header("User-Agent", "kon/reqwest")
.header("Authorization", format!("DeepL-Auth-Key {deepl_key}"))
.header("Content-Type", "application/json")
.json(&DeepLRequest {
text: vec![content.to_string()],
target_lang: "EN".to_string()
text: vec![content.to_owned()],
target_lang: "EN"
})
.send()
.await
@ -142,11 +182,17 @@ pub async fn translate(
};
if let Some(translation) = translation.translations.first() {
let quota_info = match get_quota(&deepl_key).await {
Ok(u) => &format!("-# **Quota: {}/{}**", prettify_nums(u.character_count), prettify_nums(u.character_limit)),
Err(_) => "-# *Failed to check the quota!*"
};
ctx
.send(
CreateReply::new().content(
[
format!("**Translated from {}**", prettify_lang(translation.detected_source_language.as_str())),
quota_info.to_string(),
format!("```\n{}\n```", translation.text)
]
.join("\n")
@ -162,38 +208,94 @@ pub async fn translate(
Ok(())
}
fn prettify_lang(code: &str) -> &str {
match code {
"AR" => "Arabic",
"BG" => "Bulgarian",
"CS" => "Czech",
"DA" => "Danish",
"DE" => "German",
"EL" => "Greek",
"EN" => "English",
"ES" => "Spanish",
"ET" => "Estonian",
"FI" => "Finnish",
"FR" => "French",
"HU" => "Hungarian",
"ID" => "Indonesian",
"IT" => "Italian",
"JA" => "Japanese",
"KO" => "Korean",
"LT" => "Lithuanian",
"LV" => "Latvian",
"NB" => "Norwegian Bokmål",
"NL" => "Dutch",
"PL" => "Polish",
"PT" => "Portuguese",
"RO" => "Romanian",
"RU" => "Russian",
"SK" => "Slovak",
"SL" => "Slovenian",
"SV" => "Swedish",
"TR" => "Turkish",
"UK" => "Ukrainian",
"ZH" => "Chinese",
_ => code
async fn update_locale_cache(api_key: &str) -> Result<(), reqwest::Error> {
let api_url = if api_key.ends_with(":fx") {
"https://api-free.deepl.com"
} else {
"https://api.deepl.com"
};
let languages: Vec<DeepLLanguage> = REQWEST_
.get(format!("{api_url}/v2/languages"))
.header("User-Agent", "kon/reqwest")
.header("Authorization", format!("DeepL-Auth-Key {api_key}"))
.query(&[("type", "target")])
.send()
.await?
.json()
.await?;
let mut c = LOCALE_CACHE.write().unwrap();
for language in languages {
c.insert(language.language, language.name);
}
Ok(())
}
/// List of languages that DeepL supports for translation
static LOCALE_LOOKUP: LazyLock<HashMap<&'static str, &'static str>> = LazyLock::new(|| {
let mut c = HashMap::new();
c.insert("AR", "Arabic");
c.insert("BG", "Bulgarian");
c.insert("CS", "Czech");
c.insert("DA", "Danish");
c.insert("DE", "German");
c.insert("EL", "Greek");
c.insert("EN", "English");
c.insert("ES", "Spanish");
c.insert("ET", "Estonian");
c.insert("FI", "Finnish");
c.insert("FR", "French");
c.insert("HU", "Hungarian");
c.insert("ID", "Indonesian");
c.insert("IT", "Italian");
c.insert("JA", "Japanese");
c.insert("KO", "Korean");
c.insert("LT", "Lithuanian");
c.insert("LV", "Latvian");
c.insert("NB", "Norwegian Bokmål");
c.insert("NL", "Dutch");
c.insert("PL", "Polish");
c.insert("PT", "Portuguese");
c.insert("RO", "Romanian");
c.insert("RU", "Russian");
c.insert("SK", "Slovak");
c.insert("SL", "Slovenian");
c.insert("SV", "Swedish");
c.insert("TR", "Turkish");
c.insert("UK", "Ukrainian");
c.insert("ZH", "Chinese");
c
});
fn prettify_lang(code: &str) -> String {
if let Ok(cache) = LOCALE_CACHE.read() {
if let Some(name) = cache.get(code) {
return name.clone();
}
}
LOCALE_LOOKUP.get(code).map(|&s| s.to_string()).unwrap_or_else(|| code.to_string())
}
async fn get_quota(api_key: &str) -> Result<DeepLUsage, reqwest::Error> {
let api_url = if api_key.ends_with(":fx") {
"https://api-free.deepl.com"
} else {
"https://api.deepl.com"
};
let usage: DeepLUsage = REQWEST_
.get(format!("{api_url}/v2/usage"))
.header("User-Agent", "kon/reqwest")
.header("Authorization", format!("DeepL-Auth-Key {api_key}"))
.send()
.await?
.json()
.await?;
Ok(usage)
}

@ -13,14 +13,25 @@ use {
serde_json::Value,
std::{
collections::HashMap,
sync::OnceLock,
time::Duration
}
};
async fn pms_serverstatus(url: String) -> Result<Vec<(String, Vec<Value>)>, String> {
let client = HttpClient::new();
let duration = Duration::from_secs(5);
let req = match tokio::time::timeout(duration, client.get(url.as_str(), "PMS-Status")).await {
type IdNameHashmap = HashMap<&'static str, &'static str>;
const HTTP_TIMEOUT: Duration = Duration::from_secs(5);
fn id_name_map() -> &'static IdNameHashmap {
static ID_NAME_MAP: OnceLock<IdNameHashmap> = OnceLock::new();
ID_NAME_MAP.get_or_init(|| [("wotbsg", "ASIA"), ("wowssg", "ASIA"), ("wowseu", "EU")].iter().cloned().collect())
}
async fn pms_serverstatus(
http: &HttpClient,
url: String
) -> Result<Vec<(String, Vec<Value>)>, String> {
let req = match tokio::time::timeout(HTTP_TIMEOUT, http.get(url.as_str(), "PMS-Status")).await {
Ok(result) => match result {
Ok(req) => req,
Err(e) => return Err(format!("Failed to connect: {e}"))
@ -28,7 +39,7 @@ async fn pms_serverstatus(url: String) -> Result<Vec<(String, Vec<Value>)>, Stri
Err(_) => return Err("Request timed out".to_string())
};
let response = match tokio::time::timeout(duration, req.json::<HashMap<String, Value>>()).await {
let response = match tokio::time::timeout(HTTP_TIMEOUT, req.json::<HashMap<String, Value>>()).await {
Ok(result) => match result {
Ok(data) => data,
Err(e) => return Err(format!("Failed to parse response: {e}"))
@ -57,7 +68,6 @@ async fn pms_serverstatus(url: String) -> Result<Vec<(String, Vec<Value>)>, Stri
fn process_pms_statuses(servers: Vec<(String, Vec<Value>)>) -> Vec<(String, String, bool)> {
let mut server_map: HashMap<String, Vec<(String, String)>> = HashMap::new();
let id_name_map: HashMap<&str, &str> = [("wotbsg", "ASIA"), ("wowssg", "ASIA"), ("wowseu", "EU")].iter().cloned().collect();
for (title, mapped_servers) in servers {
for server in mapped_servers {
@ -68,7 +78,7 @@ fn process_pms_statuses(servers: Vec<(String, Vec<Value>)>) -> Vec<(String, Stri
"-1" => "Offline",
_ => "Unknown"
};
let name = id_name_map.get(id).unwrap_or(&name);
let name = id_name_map().get(id).unwrap_or(&name);
server_map
.entry(title.clone())
.or_default()
@ -93,7 +103,7 @@ fn process_pms_statuses(servers: Vec<(String, Vec<Value>)>) -> Vec<(String, Stri
pub async fn wargaming(ctx: super::PoiseCtx<'_>) -> KonResult<()> {
ctx.defer().await?;
let regions = [
static REGIONS: [(&str, &str); 4] = [
("asia", "Asia (WoT)"),
("eu", "Europe (WoT)"),
("wgcb", "Console (WoTX)"),
@ -103,17 +113,18 @@ pub async fn wargaming(ctx: super::PoiseCtx<'_>) -> KonResult<()> {
let pms_base = token_path().await.wg_pms;
let mut embed = CreateEmbed::new().color(BINARY_PROPERTIES.embed_color);
let mut futures = Vec::new();
let mut region_names = Vec::new();
let http = HttpClient::new();
let mut futures = Vec::with_capacity(REGIONS.len());
let mut region_names = Vec::with_capacity(REGIONS.len());
for (region, name) in &regions {
for (region, name) in &REGIONS {
let url = if *region == "asia" {
pms_base.clone()
} else {
pms_base.replace("asia", region)
};
futures.push(pms_serverstatus(url));
futures.push(pms_serverstatus(&http, url));
region_names.push(name);
}