Implement reminding
This commit is contained in:
parent
aebbcd7815
commit
37140b5b59
2 changed files with 138 additions and 4 deletions
|
|
@ -17,7 +17,7 @@ async fn run() -> anyhow::Result<()> {
|
||||||
|
|
||||||
scheduler::reschedule_recurring_tasks(&ctx, &tasks).await?;
|
scheduler::reschedule_recurring_tasks(&ctx, &tasks).await?;
|
||||||
|
|
||||||
reminder::remind_due_tasks(&ctx, &tasks).await?;
|
reminder::remind_all_tasks(&ctx, &tasks).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
140
src/reminder.rs
140
src/reminder.rs
|
|
@ -10,7 +10,14 @@ enum BatchingInterval {
|
||||||
OnTheDay,
|
OnTheDay,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn remind_due_tasks(ctx: &crate::Context, tasks: &[task::Task]) -> anyhow::Result<()> {
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
enum ReminderType {
|
||||||
|
Basic,
|
||||||
|
Urgent,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn remind_all_tasks(ctx: &crate::Context, tasks: &[task::Task]) -> anyhow::Result<()> {
|
||||||
|
let mut reminded = 0;
|
||||||
for task in tasks {
|
for task in tasks {
|
||||||
let Some(due_date) = task.state.due_date_if_open() else {
|
let Some(due_date) = task.state.due_date_if_open() else {
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -25,12 +32,139 @@ pub async fn remind_due_tasks(ctx: &crate::Context, tasks: &[task::Task]) -> any
|
||||||
}
|
}
|
||||||
|
|
||||||
if is_overdue(ctx, due_date) {
|
if is_overdue(ctx, due_date) {
|
||||||
log::info!("Task {task} is already overdue!");
|
log::debug!("Task {task} is already overdue!");
|
||||||
}
|
}
|
||||||
|
|
||||||
log::warn!("TODO: Remind {task}");
|
if let Some((reminded_date, reminder_type)) = check_task_reminded(ctx, task).await? {
|
||||||
|
if reminder_type == ReminderType::Urgent {
|
||||||
|
log::debug!("Was already reminded urgently, skipping.");
|
||||||
|
} else if &reminded_date < due_date && is_overdue(ctx, due_date) {
|
||||||
|
log::info!("Task {task} is now overdue, reminding again urgently...");
|
||||||
|
remind_task(ctx, task, ReminderType::Urgent, batching_interval).await?;
|
||||||
|
} else {
|
||||||
|
log::debug!("Was already reminded, skipping.");
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
log::info!("Reminding {task} ...");
|
||||||
|
remind_task(ctx, task, ReminderType::Basic, batching_interval).await?;
|
||||||
|
reminded += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if reminded == 0 {
|
||||||
|
log::info!("No tasks needed to be reminded.");
|
||||||
|
} else {
|
||||||
|
log::debug!("Reminded {reminded} tasks.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
const SIGNATURE_BASIC: &'static str = "<small>\\[TaskBot Comment\\]</small>";
|
||||||
|
const SIGNATURE_URGENT: &'static str = "<small>\\[TaskBot Comment (Urgent)\\]</small>";
|
||||||
|
|
||||||
|
async fn check_task_reminded(
|
||||||
|
ctx: &crate::Context,
|
||||||
|
task: &task::Task,
|
||||||
|
) -> anyhow::Result<Option<(jiff::Zoned, ReminderType)>> {
|
||||||
|
let mut timeline = ctx
|
||||||
|
.forgejo
|
||||||
|
.issue_get_comments_and_timeline(
|
||||||
|
&ctx.owner,
|
||||||
|
&ctx.repo,
|
||||||
|
task.issue_number.into(),
|
||||||
|
forgejo_api::structs::IssueGetCommentsAndTimelineQuery {
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.all()
|
||||||
|
.await
|
||||||
|
.with_context(|| format!("Failed to fetch timeline for {task}"))?;
|
||||||
|
|
||||||
|
// Should not be necessary, but let's be safe.
|
||||||
|
timeline.sort_by_key(|event| event.created_at);
|
||||||
|
|
||||||
|
let mut has_reminder = None;
|
||||||
|
let mut has_urgent_reminder = None;
|
||||||
|
for event in timeline.iter().rev() {
|
||||||
|
match (event.r#type.as_deref(), &event.body) {
|
||||||
|
(Some("reopen"), _) => {
|
||||||
|
// When we find the event where the issue was last reopened, we stop.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
(Some("comment"), Some(body)) => {
|
||||||
|
if body.contains(SIGNATURE_URGENT) {
|
||||||
|
log::debug!("Found urgent reminder for issue #{}.", task.issue_number);
|
||||||
|
has_urgent_reminder = Some(created_at_time(event)?);
|
||||||
|
} else if body.contains(SIGNATURE_BASIC) {
|
||||||
|
log::debug!("Found reminder for issue #{}.", task.issue_number);
|
||||||
|
has_reminder = Some(created_at_time(event)?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Ignore all other events.
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let res = has_urgent_reminder
|
||||||
|
.map(|t| (t, ReminderType::Urgent))
|
||||||
|
.or_else(|| has_reminder.map(|t| (t, ReminderType::Basic)));
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn created_at_time(ev: &forgejo_api::structs::TimelineComment) -> anyhow::Result<jiff::Zoned> {
|
||||||
|
Ok(util::time_to_jiff(ev.created_at.with_context(|| {
|
||||||
|
format!("Timeline event {:?} without created_at time", ev.r#type,)
|
||||||
|
})?))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn remind_task(
|
||||||
|
ctx: &crate::Context,
|
||||||
|
task: &task::Task,
|
||||||
|
reminder_type: ReminderType,
|
||||||
|
batching_interval: BatchingInterval,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let mut body = match (reminder_type, batching_interval) {
|
||||||
|
(ReminderType::Basic, BatchingInterval::Monthly) => format!(
|
||||||
|
"\
|
||||||
|
Beep boop. This task is due this month, go take care of it!"
|
||||||
|
),
|
||||||
|
(ReminderType::Basic, BatchingInterval::Weekly) => format!(
|
||||||
|
"\
|
||||||
|
Beep boop. This task is due this week, go take care of it!"
|
||||||
|
),
|
||||||
|
(ReminderType::Basic, BatchingInterval::OnTheDay) => format!(
|
||||||
|
"\
|
||||||
|
Beep boop. This task is due today, go take care of it!"
|
||||||
|
),
|
||||||
|
(ReminderType::Urgent, _) => format!(
|
||||||
|
"\
|
||||||
|
Hello again. This task is overdue, please take care of it ASAP!"
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
let signature = match reminder_type {
|
||||||
|
ReminderType::Basic => SIGNATURE_BASIC,
|
||||||
|
ReminderType::Urgent => SIGNATURE_URGENT,
|
||||||
|
};
|
||||||
|
|
||||||
|
let body = body + "\n\n" + signature;
|
||||||
|
|
||||||
|
ctx.forgejo
|
||||||
|
.issue_create_comment(
|
||||||
|
&ctx.owner,
|
||||||
|
&ctx.repo,
|
||||||
|
task.issue_number.into(),
|
||||||
|
forgejo_api::structs::CreateIssueCommentOption {
|
||||||
|
body: body,
|
||||||
|
updated_at: None,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.with_context(|| format!("Could not create comment to remind task {task}"))?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue