Initial commit

This commit is contained in:
2022-10-19 10:23:55 +02:00
commit e144a0aecc
13 changed files with 2544 additions and 0 deletions

182
src/bot.rs Normal file
View File

@@ -0,0 +1,182 @@
use anyhow::{Error, Result};
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 announcement = bot
.send_message(
ChatId(chat_info.id),
format!("Nächster Termin: {}", date_str),
)
.await?;
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(announcement.id.0),
))
.execute(db)
})?;
chat_info.next_appointment = Some(appointment);
chat_info.last_reminder = Some(now);
chat_info.pinned_message_id = Some(announcement.id.0);
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(false);
pin_message.await?;
Ok(())
}