Files
s3bot/src/bot/handlers/basic/currency_converter.rs
2023-02-21 19:56:36 +00:00

177 lines
5.1 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use std::{collections::HashMap, time::Duration};
use grammers_client::{Client, InputMessage, Update};
use regex::Regex;
use crate::{
bot::{filters::base::Filter, handlers::Handler},
utils::messages::get_message,
};
lazy_static::lazy_static! {
static ref SUPPORTED_CURS: Vec<&'static str> = vec![
"GBP",
"HUF",
"USD",
"EUR",
"CNY",
"NOK",
"UAH",
"SEK",
"CHF",
"KRW",
"JPY",
"KZT",
"PLN",
"TRY",
"AMD",
"RSD",
];
static ref CONVERTION_ALIASES: HashMap<&'static str, &'static str> = HashMap::from(
[
// GBP
("фунт", "GBP"),
// USD
("бакс", "USD"),
("доллар", "USD"),
// EUR
("евро", "EUR"),
// JPY
("иен", "JPY"),
("йен", "JPY"),
// KRW
("вон", "KRW"),
// CHF
("франк", "CHF"),
// SEK
("крон", "CHF"),
// CNY
("юан", "CNY"),
// UAH
("гривна", "UAH"),
("гривны", "UAH"),
("гривен", "UAH"),
("грен", "UAH"),
// KZT
("тенге", "KZT"),
("тэнге", "KZT"),
// PLN
("злот", "PLN"),
// TRY
("лир", "TRY"),
// AMD
("драм", "AMD"),
// RSD
("динар", "RSD"),
]
);
static ref CUR_REGEX: Regex = {
#[allow(clippy::clone_double_ref)]
let a = CONVERTION_ALIASES.keys()
.copied()
.chain(SUPPORTED_CURS.iter().copied())
.collect::<Vec<_>>()
.join("|");
Regex::new(format!(r"\s*(?P<cur_value>\d+([\.,]\d+)?)\s+(?P<cur_name>{a})").as_str()).unwrap()
};
}
#[derive(Clone)]
pub struct CurrencyTextFilter;
#[derive(Clone)]
pub struct CurrencyConverter {
client: reqwest::Client,
}
impl CurrencyConverter {
pub fn new() -> anyhow::Result<Self> {
let client = reqwest::ClientBuilder::new()
.timeout(Duration::from_secs(2))
.gzip(true)
.build()?;
Ok(Self { client })
}
}
/// This filter check if the message matches regex for currencies.
impl Filter for CurrencyTextFilter {
fn filter(&self, update: &Update) -> anyhow::Result<bool> {
let Some(message) = get_message(update) else {
return Ok(false);
};
Ok(CUR_REGEX.is_match(message.text()))
}
}
#[async_trait::async_trait]
impl Handler for CurrencyConverter {
async fn react(&self, _: &Client, update: &Update) -> anyhow::Result<()> {
let Some(message) = get_message(update) else{ return Ok(())};
let response = self
.client
.get("https://www.cbr-xml-daily.ru/daily_json.js")
.send()
.await?
.error_for_status()?
.json::<serde_json::Value>()
.await?;
let Some(valutes) = response
.get("Valute")
.and_then(serde_json::Value::as_object) else{
log::warn!("Can't get valutes fom response.");
return Ok(());
};
let mut calucates = Vec::new();
for capture in CUR_REGEX.captures_iter(message.text()) {
// We parse supplied value from message
let Some(num_value) = capture
.name("cur_value")
// Convert match to string.
.map(|mtch| mtch.as_str())
// Parse it.
.and_then(|val| val.parse::<f64>().ok()) else{
continue;
};
let cur_name = capture.name("cur_name").map(|mtch| mtch.as_str());
let Some(cur_name) = cur_name
// We check if the value is an alias.
.and_then(|val| CONVERTION_ALIASES.get(val).copied())
// get previous value if not.
.or(cur_name) else{
continue;
};
let calculated = valutes
.get(cur_name)
.and_then(|info| info.get("Value"))
.map(ToString::to_string)
.and_then(|value| value.as_str().parse::<f64>().ok())
.map(|multiplier| multiplier * num_value);
if let Some(value) = calculated {
calucates.push(format!(
"<pre>{num_value} {cur_name} = {value:.2} RUB</pre><br>"
));
}
}
if !calucates.is_empty() {
let mut bot_response =
String::from("<b>Полагаясь на текущий курс валют могу сказать следующее:</b>\n\n");
for calc in calucates {
bot_response.push_str(calc.as_str());
}
message
// We send it as silent, so we can filter this message later.
.reply(InputMessage::html(bot_response).silent(true))
.await?;
}
Ok(())
}
}