11
Cargo.lock
generated
11
Cargo.lock
generated
@ -1355,6 +1355,15 @@ dependencies = [
|
||||
"windows-sys 0.45.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.10.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.5"
|
||||
@ -2152,8 +2161,10 @@ dependencies = [
|
||||
"grammers-client",
|
||||
"grammers-session",
|
||||
"grammers-tl-types",
|
||||
"itertools",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"num-integer",
|
||||
"rand 0.8.5",
|
||||
"rayon",
|
||||
"regex",
|
||||
|
@ -35,3 +35,5 @@ tokio = { version = "1.25.0", features = [
|
||||
"macros",
|
||||
"rt-multi-thread",
|
||||
] }
|
||||
num-integer = "0.1.45"
|
||||
itertools = "0.10.5"
|
||||
|
@ -2,4 +2,5 @@ pub mod currency_converter;
|
||||
pub mod get_chat_id;
|
||||
pub mod help;
|
||||
pub mod notify_all;
|
||||
pub mod time_converter;
|
||||
pub mod weather_forecaster;
|
||||
|
175
src/bot/handlers/basic/time_converter.rs
Normal file
175
src/bot/handlers/basic/time_converter.rs
Normal file
@ -0,0 +1,175 @@
|
||||
use crate::{bot::handlers::Handler, utils::messages::get_message};
|
||||
use chrono::{FixedOffset, NaiveDateTime, NaiveTime, TimeZone, Utc};
|
||||
use grammers_client::{Client, InputMessage, Update};
|
||||
use itertools::Itertools;
|
||||
use num_integer::div_floor;
|
||||
|
||||
const HOUR: i32 = 3600;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TimeConverter;
|
||||
|
||||
#[allow(clippy::trivially_copy_pass_by_ref)]
|
||||
pub fn to_utc_name(offset: &FixedOffset) -> String {
|
||||
let seconds = offset.local_minus_utc();
|
||||
let hours = div_floor(seconds, HOUR);
|
||||
if hours >= 0 {
|
||||
format!("UTC+{hours}")
|
||||
} else {
|
||||
format!("UTC{hours}")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn convert_time(offsets: &[FixedOffset], times: &[NaiveTime]) -> Vec<String> {
|
||||
let mut replies = Vec::new();
|
||||
let now = Utc::now();
|
||||
|
||||
let Some(main_offset) = offsets.get(0) else {
|
||||
return vec![];
|
||||
};
|
||||
|
||||
for time in times {
|
||||
let dt = NaiveDateTime::new(now.date_naive(), *time);
|
||||
let Some(start_time) = main_offset.from_local_datetime(&dt).latest() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
for offset in offsets {
|
||||
if offset == main_offset && offsets.len() > 1 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let end_time = start_time.with_timezone(offset);
|
||||
|
||||
replies.push(format!(
|
||||
"{} {} = {} {}",
|
||||
start_time.format("%H:%M"),
|
||||
to_utc_name(main_offset),
|
||||
end_time.format("%H:%M"),
|
||||
to_utc_name(offset)
|
||||
));
|
||||
}
|
||||
}
|
||||
replies
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl Handler for TimeConverter {
|
||||
async fn react(&self, _: &Client, update: &Update) -> anyhow::Result<()> {
|
||||
let Some(message) = get_message(update) else{return Ok(());};
|
||||
|
||||
let mut offsets = Vec::new();
|
||||
let mut times = Vec::new();
|
||||
for part in message.text().strip_prefix(".t").unwrap_or("").split(' ') {
|
||||
if let Some(offset) = part
|
||||
.parse::<i32>()
|
||||
.ok()
|
||||
.and_then(|offset| FixedOffset::east_opt(offset * HOUR))
|
||||
{
|
||||
offsets.push(offset);
|
||||
} else if let Ok(naive_time) = NaiveTime::parse_from_str(part, "%H:%M") {
|
||||
times.push(naive_time);
|
||||
}
|
||||
}
|
||||
|
||||
if offsets.is_empty() && times.is_empty() {
|
||||
message
|
||||
.reply(format!(
|
||||
"Текущее время в UTC+0: {}",
|
||||
Utc::now().time().format("%H:%M")
|
||||
))
|
||||
.await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if offsets.len() > 50 || times.len() > 50 {
|
||||
message.reply("Ты меня походу спамишь, дядь.").await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if offsets.is_empty() {
|
||||
message
|
||||
.reply("Добавь оффсеты. Например: .t 1 +1 -1")
|
||||
.await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if times.is_empty() {
|
||||
offsets = [FixedOffset::east_opt(0).unwrap()]
|
||||
.into_iter()
|
||||
.chain(offsets.into_iter())
|
||||
.collect::<Vec<_>>();
|
||||
times.push(Utc::now().time());
|
||||
}
|
||||
|
||||
let replies = convert_time(
|
||||
offsets.into_iter().unique().collect_vec().as_slice(),
|
||||
times.into_iter().unique().collect_vec().as_slice(),
|
||||
)
|
||||
.into_iter()
|
||||
.map(|reply| format!("<pre>{reply}</pre><br>"))
|
||||
.join("\n");
|
||||
|
||||
if replies.trim().is_empty() {
|
||||
message.reply("Что-то я ничего не смог насчитать.").await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
message
|
||||
.reply(InputMessage::html(format!(
|
||||
"<b>Вот что я насчитал по времени: </b>\n\n{replies}"
|
||||
)))
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use chrono::{FixedOffset, NaiveTime};
|
||||
|
||||
use super::{convert_time, HOUR};
|
||||
|
||||
#[test]
|
||||
pub fn test_time_conversion() {
|
||||
let replies = convert_time(
|
||||
&[
|
||||
FixedOffset::east_opt(0).unwrap(),
|
||||
FixedOffset::east_opt(1 * HOUR).unwrap(),
|
||||
FixedOffset::east_opt(2 * HOUR).unwrap(),
|
||||
],
|
||||
&[NaiveTime::from_hms_opt(0, 0, 0).unwrap()],
|
||||
);
|
||||
|
||||
assert_eq!(replies.len(), 2);
|
||||
assert_eq!(
|
||||
replies,
|
||||
vec![
|
||||
String::from("00:00 UTC+0 = 01:00 UTC+1"),
|
||||
String::from("00:00 UTC+0 = 02:00 UTC+2"),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_time_conversion_negatives() {
|
||||
let replies = convert_time(
|
||||
&[
|
||||
FixedOffset::east_opt(-3 * HOUR).unwrap(),
|
||||
FixedOffset::east_opt(1 * HOUR).unwrap(),
|
||||
FixedOffset::east_opt(2 * HOUR).unwrap(),
|
||||
],
|
||||
&[NaiveTime::from_hms_opt(0, 0, 0).unwrap()],
|
||||
);
|
||||
|
||||
assert_eq!(replies.len(), 2);
|
||||
assert_eq!(
|
||||
replies,
|
||||
vec![
|
||||
String::from("00:00 UTC-3 = 04:00 UTC+1"),
|
||||
String::from("00:00 UTC-3 = 05:00 UTC+2"),
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
@ -21,6 +21,7 @@ use super::{
|
||||
get_chat_id::GetChatId,
|
||||
help::Help,
|
||||
notify_all::NotifyAll,
|
||||
time_converter::TimeConverter,
|
||||
weather_forecaster::WeatherForecaster,
|
||||
},
|
||||
fun::{
|
||||
@ -155,7 +156,13 @@ async fn run(args: BotConfig, client: Client) -> anyhow::Result<()> {
|
||||
FilteredHandler::new(NotifyAll)
|
||||
.add_filter(UpdateTypeFilter(&[UpdateType::New]))
|
||||
.add_filter(SilentFilter)
|
||||
.add_filter(TextFilter(&["@all"], TextMatchMethod::Contains)),
|
||||
.add_filter(TextFilter(&["@all"], TextMatchMethod::Contains))
|
||||
.add_middleware::<MembersCount<100>>(),
|
||||
// Time conversion utils
|
||||
FilteredHandler::new(TimeConverter)
|
||||
.add_filter(UpdateTypeFilter(&[UpdateType::New]))
|
||||
.add_filter(SilentFilter)
|
||||
.add_filter(TextFilter(&[".t"], TextMatchMethod::StartsWith)),
|
||||
];
|
||||
|
||||
let mut errors_count = 0;
|
||||
|
Reference in New Issue
Block a user