198 lines
5.2 KiB
Rust
198 lines
5.2 KiB
Rust
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<Bot>, db: Database) {
|
|
Dispatcher::builder(bot, build_handler_chain())
|
|
.dependencies(deps![db, InMemStorage::<()>::new()])
|
|
.build()
|
|
.dispatch()
|
|
.await;
|
|
}
|
|
|
|
fn build_handler_chain() -> UpdateHandler<Error> {
|
|
let command_handler = teloxide::filter_command::<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::<Update, InMemStorage<()>, (), _>()
|
|
.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<Bot>, 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::<Utc> {
|
|
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<Bot>,
|
|
chat_info: &mut ChatInfo<Utc>,
|
|
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::<DbChat>(db)
|
|
})?;
|
|
|
|
let entry = ChatInfo::from(entry);
|
|
|
|
let new_appointment = entry
|
|
.next_appointment
|
|
.as_ref()
|
|
.map(|db_appointment| db_appointment.start != appointment.start)
|
|
.unwrap_or(true);
|
|
|
|
if !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(())
|
|
}
|