Optimize several parts

This commit is contained in:
Nwero 2025-04-13 02:37:43 +10:00
parent 55cbe57728
commit 452bd92eb1
5 changed files with 167 additions and 54 deletions

4
Cargo.lock generated

@ -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",

@ -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);
}