use anyhow::{Error, Result}; use chrono::Datelike; use chrono::Utc; use chrono_tz::Europe; use diesel::Connection; use diesel::ExpressionMethods; use diesel::QueryDsl; use diesel::RunQueryDsl; use teloxide::adaptors::Throttle; use teloxide::dispatching::dialogue; use teloxide::dispatching::dialogue::InMemStorage; use teloxide::dispatching::{UpdateFilterExt, UpdateHandler}; use teloxide::dptree::{case, deps}; use teloxide::prelude::Dispatcher; use teloxide::requests::Requester; use teloxide::types::ChatId; use teloxide::types::ChatMemberKind; use teloxide::types::ChatMemberUpdated; use teloxide::types::MessageId; use teloxide::types::{Message, Update}; use teloxide::utils::command::BotCommands; use teloxide::Bot; use crate::appointment::fetch_next_appointment; use crate::db::ChatInfo; use crate::db::DbChat; use crate::{schema, Database}; #[derive(BotCommands, Clone)] #[command(rename_rule = "lowercase")] pub enum Command { #[command()] SetCalendar, } pub async fn spawn(bot: Throttle, db: Database) { Dispatcher::builder(bot, build_handler_chain()) .dependencies(deps![db, InMemStorage::<()>::new()]) .build() .dispatch() .await; } fn build_handler_chain() -> UpdateHandler { let command_handler = teloxide::filter_command::() .branch(case![Command::SetCalendar].endpoint(set_calendar)); let my_chat_member_handler = Update::filter_my_chat_member().endpoint(handle_my_chat_member); let message_handler = Update::filter_message().branch(command_handler); dialogue::enter::, (), _>() .branch(my_chat_member_handler) .branch(message_handler) } async fn handle_my_chat_member(msg: ChatMemberUpdated, db: Database) -> Result<(), Error> { match msg.new_chat_member.kind { ChatMemberKind::Left | ChatMemberKind::Banned(_) => { db.lock().await.transaction::<_, Error, _>(|db| { use schema::chat::dsl::*; diesel::delete(chat.filter(telegram_id.eq(msg.chat.id.0))).execute(db)?; Ok(()) })?; } _ => {} } Ok(()) } async fn set_calendar(bot: Throttle, msg: Message, db: Database) -> Result<(), Error> { let url = msg.text().map(|url| url.splitn(2, " ").nth(1)).flatten(); if url.is_none() { return Ok(()); } let url = url.unwrap().trim(); if !url.starts_with("http") { return Ok(()); } db.lock().await.transaction::<_, Error, _>(|db| { use schema::chat::dsl::*; diesel::delete(chat.filter(telegram_id.eq(msg.chat.id.0))).execute(db)?; diesel::insert_into(chat) .values((telegram_id.eq(msg.chat.id.0), calendar.eq(url))) .execute(db)?; Ok(()) })?; let mut chat_info = ChatInfo:: { id: msg.chat.id.0, calendar: url.to_owned(), next_appointment: None, last_reminder: None, pinned_message_id: None, }; fetch_and_announce_appointment(&bot, &mut chat_info, &db).await?; Ok(()) } pub async fn fetch_and_announce_appointment( bot: &Throttle, chat_info: &mut ChatInfo, db: &Database, ) -> Result<()> { let appointment = match fetch_next_appointment(&chat_info.calendar).await? { Some(appointment) => appointment, None => return Ok(()), }; let appointment = appointment.with_timezone(&Utc); let entry = db.lock().await.transaction(|db| { use schema::chat::dsl::*; chat.filter(telegram_id.eq(chat_info.id)) .first::(db) })?; let entry = ChatInfo::from(entry); let is_new_appointment = entry .next_appointment .as_ref() .map(|db_appointment| db_appointment.start != appointment.start) .unwrap_or(true); if !is_new_appointment { return Ok(()); } let now = Utc::now(); let old_appointment_running = entry .next_appointment .as_ref() .map(|appointment| appointment.start <= now && appointment.end >= now) .unwrap_or(false); if old_appointment_running { return Ok(()); } let date_str = appointment .start .with_timezone(&Europe::Berlin) .format("%d.%m.%Y %H:%M"); let weekday = match appointment.start.weekday() { chrono::Weekday::Mon => "Montag", chrono::Weekday::Tue => "Dienstag", chrono::Weekday::Wed => "Mittwoch", chrono::Weekday::Thu => "Donnerstag", chrono::Weekday::Fri => "Freitag", chrono::Weekday::Sat => "Samstag", chrono::Weekday::Sun => "Sonntag", }; let announcement = bot .send_message( ChatId(chat_info.id), format!("Nächster Termin: {}, {}", weekday, date_str), ) .await?; if let Some(pinned_message_id) = entry.pinned_message_id { let mut unpin_message = bot.unpin_chat_message(ChatId(chat_info.id)); unpin_message.message_id = Some(MessageId(pinned_message_id)); _ = unpin_message.await; } let mut pin_message = bot.pin_chat_message(announcement.chat.id, announcement.id); pin_message.disable_notification = Some(true); let pin_sucessful = pin_message.await.is_ok(); chat_info.next_appointment = Some(appointment.clone()); chat_info.last_reminder = Some(now); chat_info.pinned_message_id = if pin_sucessful { Some(announcement.id.0) } else { None }; db.lock().await.transaction(|db| { use schema::chat::dsl::*; diesel::update(chat.filter(telegram_id.eq(chat_info.id))) .set(( next_appointment_start.eq(appointment.start.timestamp()), next_appointment_end.eq(appointment.end.timestamp()), last_reminder.eq(now.timestamp()), pinned_message_id.eq(chat_info.pinned_message_id), )) .execute(db) })?; Ok(()) }