Add DeepL translater

This commit is contained in:
Nwero 2025-03-25 16:23:12 +11:00
parent 39c668a6e9
commit 0ac002f59a
8 changed files with 211 additions and 7 deletions

2
.gitignore vendored

@ -1,3 +1,3 @@
target
.env
.env*
*.log

4
Cargo.lock generated

@ -1091,7 +1091,7 @@ dependencies = [
[[package]]
name = "kon"
version = "0.6.8"
version = "0.6.9"
dependencies = [
"kon_cmds",
"kon_libs",
@ -1102,7 +1102,7 @@ dependencies = [
[[package]]
name = "kon_cmds"
version = "0.1.5"
version = "0.1.6"
dependencies = [
"dashmap 6.1.0",
"kon_libs",

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

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

@ -1,5 +1,6 @@
mod ilo;
mod status;
mod translate;
mod uptime;
use kon_libs::{
@ -12,6 +13,7 @@ use kon_libs::{
use {
ilo::ilo,
status::status,
translate::translate,
uptime::uptime
};
@ -21,7 +23,7 @@ macro_rules! commands {
}
}
pub fn register_cmds() -> Vec<poise::Command<KonData, KonError>> { commands!(deploy, ping, ilo, status, uptime) }
pub fn register_cmds() -> Vec<poise::Command<KonData, KonError>> { commands!(deploy, ping, ilo, status, translate, uptime) }
/// Deploy the commands globally or in a guild
#[poise::command(prefix_command, owners_only, guild_only)]
@ -33,6 +35,6 @@ pub async fn deploy(ctx: PoiseCtx<'_>) -> KonResult<()> {
/// Check if the bot is alive
#[poise::command(slash_command, install_context = "Guild|User", interaction_context = "Guild|BotDm|PrivateChannel")]
pub async fn ping(ctx: PoiseCtx<'_>) -> KonResult<()> {
ctx.reply(format!("Powong! `{:.2?}`", ctx.ping().await)).await?;
ctx.reply(format!("Powong! **{:.0?}ms**", ctx.ping().await)).await?;
Ok(())
}

@ -0,0 +1,199 @@
use {
kon_libs::KonResult,
poise::{
CreateReply,
serenity_prelude::Message
},
serde::{
Deserialize,
Serialize
}
};
#[derive(Serialize)]
struct DeepLRequest {
text: Vec<String>,
target_lang: String
}
#[derive(Deserialize)]
struct DeepLResponse {
translations: Vec<Translation>
}
#[derive(Deserialize)]
struct Translation {
text: String,
detected_source_language: String
}
/// Translate a given message using DeepL
#[poise::command(
context_menu_command = "Translate via DeepL",
install_context = "Guild|User",
interaction_context = "Guild|BotDm|PrivateChannel"
)]
pub async fn translate(
ctx: super::PoiseCtx<'_>,
message: Message
) -> KonResult<()> {
let content = message.content.trim();
if content.is_empty() {
ctx.send(CreateReply::new().content("Nothing to translate!").ephemeral(true)).await?;
return Ok(());
}
let deepl_key = std::env::var("KON_DEEPL").expect("No 'KON_DEEPL' key found!");
if deepl_key.is_empty() {
ctx
.send(
CreateReply::new()
.content("Can't translate this message, see console for more info!")
.ephemeral(true)
)
.await?;
return Ok(());
}
ctx.defer().await?;
let api_url = if deepl_key.ends_with(":fx") {
"https://api-free.deepl.com"
} else {
"https://api.deepl.com"
};
let client = reqwest::Client::new();
let resp = match client
.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()
})
.send()
.await
{
Ok(r) => r,
Err(e) => {
ctx
.send(CreateReply::new().content(format!("**(DeepL) Service error:** {e}")).ephemeral(true))
.await?;
return Ok(());
}
};
match resp.status().as_u16() {
200 => (),
403 => {
ctx
.send(CreateReply::new().content("Not authenticated to DeepL API").ephemeral(true))
.await?;
return Ok(());
},
429 => {
ctx
.send(CreateReply::new().content("DeepL requests ratelimited, slow down!").ephemeral(true))
.await?;
return Ok(());
},
456 => {
ctx
.send(
CreateReply::new()
.content("Quota exceeded, used up all of 500k characters for this month!\nConsider upgrading to **Pro** if still relying on DeepL!")
.ephemeral(true)
)
.await?;
return Ok(());
},
500 => {
ctx
.send(
CreateReply::new()
.content("DeepL service gave an internal server error, try again later!")
.ephemeral(true)
)
.await?;
return Ok(());
},
_ => {
ctx
.send(
CreateReply::new()
.content(format!("Unknown status code, DeepL returned with HTTP {}", resp.status().as_u16()))
.ephemeral(true)
)
.await?;
return Ok(());
}
}
let translation: DeepLResponse = match resp.json().await {
Ok(d) => d,
Err(e) => {
ctx
.send(CreateReply::new().content(format!("**(Kon) Parsing error:** {e}")).ephemeral(true))
.await?;
return Ok(());
}
};
if let Some(translation) = translation.translations.first() {
ctx
.send(
CreateReply::new().content(
[
format!("**Translated from {}**", prettify_lang(translation.detected_source_language.as_str())),
format!("```\n{}\n```", translation.text)
]
.join("\n")
)
)
.await?;
} else {
ctx
.send(CreateReply::new().content("DeepL didn't send translated text back!").ephemeral(true))
.await?;
}
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
}
}

@ -4,6 +4,7 @@ services:
#image: git.toast-server.net/nwerosama/kon:master
build: .
restart: unless-stopped
env_file: .env.bot
depends_on:
- cache
cache:

2
run.sh

@ -1,3 +1,5 @@
#!/bin/bash
export $(grep -v '^#' .env.bot | xargs)
clear && cargo fmt && cargo run kon_dev
unset $(grep -v '^#' .env.bot | cut -d= -f1)