241 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			241 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
use chrono::{naive::NaiveDateTime, Utc};
 | 
						|
use futures::TryFutureExt;
 | 
						|
use log::warn;
 | 
						|
use rocket::serde::json::json;
 | 
						|
use serde::{Deserialize, Serialize};
 | 
						|
use serenity::{client::Context, model::id::UserId};
 | 
						|
use sqlx::types::Json;
 | 
						|
 | 
						|
use crate::utils::check_subscription;
 | 
						|
use crate::web::{
 | 
						|
    consts::{
 | 
						|
        DAY, MAX_CONTENT_LENGTH, MAX_EMBED_AUTHOR_LENGTH, MAX_EMBED_DESCRIPTION_LENGTH,
 | 
						|
        MAX_EMBED_FIELDS, MAX_EMBED_FIELD_TITLE_LENGTH, MAX_EMBED_FIELD_VALUE_LENGTH,
 | 
						|
        MAX_EMBED_FOOTER_LENGTH, MAX_EMBED_TITLE_LENGTH, MAX_NAME_LENGTH, MAX_URL_LENGTH,
 | 
						|
        MIN_INTERVAL,
 | 
						|
    },
 | 
						|
    guards::transaction::Transaction,
 | 
						|
    routes::{
 | 
						|
        dashboard::{create_database_channel, generate_uid, name_default, Attachment, EmbedField},
 | 
						|
        JsonResult,
 | 
						|
    },
 | 
						|
    Error,
 | 
						|
};
 | 
						|
 | 
						|
#[derive(Serialize, Deserialize)]
 | 
						|
pub struct Reminder {
 | 
						|
    pub attachment: Option<Attachment>,
 | 
						|
    pub attachment_name: Option<String>,
 | 
						|
    pub content: String,
 | 
						|
    pub embed_author: String,
 | 
						|
    pub embed_author_url: Option<String>,
 | 
						|
    pub embed_color: u32,
 | 
						|
    pub embed_description: String,
 | 
						|
    pub embed_footer: String,
 | 
						|
    pub embed_footer_url: Option<String>,
 | 
						|
    pub embed_image_url: Option<String>,
 | 
						|
    pub embed_thumbnail_url: Option<String>,
 | 
						|
    pub embed_title: String,
 | 
						|
    pub embed_fields: Option<Json<Vec<EmbedField>>>,
 | 
						|
    pub enabled: bool,
 | 
						|
    pub expires: Option<NaiveDateTime>,
 | 
						|
    pub interval_seconds: Option<u32>,
 | 
						|
    pub interval_days: Option<u32>,
 | 
						|
    pub interval_months: Option<u32>,
 | 
						|
    #[serde(default = "name_default")]
 | 
						|
    pub name: String,
 | 
						|
    pub tts: bool,
 | 
						|
    #[serde(default)]
 | 
						|
    pub uid: String,
 | 
						|
    pub utc_time: NaiveDateTime,
 | 
						|
}
 | 
						|
 | 
						|
pub async fn create_reminder(
 | 
						|
    ctx: &Context,
 | 
						|
    transaction: &mut Transaction<'_>,
 | 
						|
    user_id: UserId,
 | 
						|
    reminder: Reminder,
 | 
						|
) -> JsonResult {
 | 
						|
    let channel = user_id
 | 
						|
        .create_dm_channel(&ctx)
 | 
						|
        .map_err(|e| Error::Serenity(e))
 | 
						|
        .and_then(|dm_channel| create_database_channel(&ctx, dm_channel.id, transaction))
 | 
						|
        .await;
 | 
						|
 | 
						|
    if let Err(e) = channel {
 | 
						|
        warn!("`create_database_channel` returned an error code: {:?}", e);
 | 
						|
 | 
						|
        // Provide more specific error messages based on the error type
 | 
						|
        let error_msg = match e {
 | 
						|
            Error::MissingDiscordPermission(permission) => format!(
 | 
						|
                "Please ensure the bot has the \"{}\" permission in the channel",
 | 
						|
                permission
 | 
						|
            ),
 | 
						|
            _ => "Failed to configure channel for reminders.".to_string(),
 | 
						|
        };
 | 
						|
 | 
						|
        return Err(json!({"error": error_msg}));
 | 
						|
    }
 | 
						|
 | 
						|
    let channel = channel.unwrap();
 | 
						|
 | 
						|
    // validate lengths
 | 
						|
    check_length!(MAX_NAME_LENGTH, reminder.name);
 | 
						|
    check_length!(MAX_CONTENT_LENGTH, reminder.content);
 | 
						|
    check_length!(MAX_EMBED_DESCRIPTION_LENGTH, reminder.embed_description);
 | 
						|
    check_length!(MAX_EMBED_TITLE_LENGTH, reminder.embed_title);
 | 
						|
    check_length!(MAX_EMBED_AUTHOR_LENGTH, reminder.embed_author);
 | 
						|
    check_length!(MAX_EMBED_FOOTER_LENGTH, reminder.embed_footer);
 | 
						|
    check_length_opt!(MAX_EMBED_FIELDS, reminder.embed_fields);
 | 
						|
    if let Some(fields) = &reminder.embed_fields {
 | 
						|
        for field in &fields.0 {
 | 
						|
            check_length!(MAX_EMBED_FIELD_VALUE_LENGTH, field.value);
 | 
						|
            check_length!(MAX_EMBED_FIELD_TITLE_LENGTH, field.title);
 | 
						|
        }
 | 
						|
    }
 | 
						|
    check_length_opt!(
 | 
						|
        MAX_URL_LENGTH,
 | 
						|
        reminder.embed_footer_url,
 | 
						|
        reminder.embed_thumbnail_url,
 | 
						|
        reminder.embed_author_url,
 | 
						|
        reminder.embed_image_url
 | 
						|
    );
 | 
						|
 | 
						|
    // validate urls
 | 
						|
    check_url_opt!(
 | 
						|
        reminder.embed_footer_url,
 | 
						|
        reminder.embed_thumbnail_url,
 | 
						|
        reminder.embed_author_url,
 | 
						|
        reminder.embed_image_url
 | 
						|
    );
 | 
						|
 | 
						|
    // validate time and interval
 | 
						|
    if reminder.utc_time < Utc::now().naive_utc() {
 | 
						|
        return Err(json!({"error": "Time must be in the future"}));
 | 
						|
    }
 | 
						|
    if reminder.interval_seconds.is_some()
 | 
						|
        || reminder.interval_days.is_some()
 | 
						|
        || reminder.interval_months.is_some()
 | 
						|
    {
 | 
						|
        if reminder.interval_months.unwrap_or(0) * 30 * DAY as u32
 | 
						|
            + reminder.interval_days.unwrap_or(0) * DAY as u32
 | 
						|
            + reminder.interval_seconds.unwrap_or(0)
 | 
						|
            < *MIN_INTERVAL
 | 
						|
        {
 | 
						|
            return Err(json!({"error": "Interval too short"}));
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    // check patreon if necessary
 | 
						|
    if reminder.interval_seconds.is_some()
 | 
						|
        || reminder.interval_days.is_some()
 | 
						|
        || reminder.interval_months.is_some()
 | 
						|
    {
 | 
						|
        if !check_subscription(&ctx, transaction.executor(), user_id, None).await {
 | 
						|
            return Err(json!({"error": "Patreon is required to set intervals"}));
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    let name = if reminder.name.is_empty() { name_default() } else { reminder.name.clone() };
 | 
						|
    let new_uid = generate_uid();
 | 
						|
 | 
						|
    // write to db
 | 
						|
    match sqlx::query!(
 | 
						|
        "INSERT INTO reminders (
 | 
						|
         uid,
 | 
						|
         attachment,
 | 
						|
         attachment_name,
 | 
						|
         channel_id,
 | 
						|
         content,
 | 
						|
         embed_author,
 | 
						|
         embed_author_url,
 | 
						|
         embed_color,
 | 
						|
         embed_description,
 | 
						|
         embed_footer,
 | 
						|
         embed_footer_url,
 | 
						|
         embed_image_url,
 | 
						|
         embed_thumbnail_url,
 | 
						|
         embed_title,
 | 
						|
         embed_fields,
 | 
						|
         enabled,
 | 
						|
         expires,
 | 
						|
         interval_seconds,
 | 
						|
         interval_days,
 | 
						|
         interval_months,
 | 
						|
         name,
 | 
						|
         tts,
 | 
						|
         `utc_time`
 | 
						|
        ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
 | 
						|
        new_uid,
 | 
						|
        reminder.attachment,
 | 
						|
        reminder.attachment_name,
 | 
						|
        channel,
 | 
						|
        reminder.content,
 | 
						|
        reminder.embed_author,
 | 
						|
        reminder.embed_author_url,
 | 
						|
        reminder.embed_color,
 | 
						|
        reminder.embed_description,
 | 
						|
        reminder.embed_footer,
 | 
						|
        reminder.embed_footer_url,
 | 
						|
        reminder.embed_image_url,
 | 
						|
        reminder.embed_thumbnail_url,
 | 
						|
        reminder.embed_title,
 | 
						|
        reminder.embed_fields,
 | 
						|
        reminder.enabled,
 | 
						|
        reminder.expires,
 | 
						|
        reminder.interval_seconds,
 | 
						|
        reminder.interval_days,
 | 
						|
        reminder.interval_months,
 | 
						|
        name,
 | 
						|
        reminder.tts,
 | 
						|
        reminder.utc_time,
 | 
						|
    )
 | 
						|
    .execute(transaction.executor())
 | 
						|
    .await
 | 
						|
    {
 | 
						|
        Ok(_) => sqlx::query_as_unchecked!(
 | 
						|
            Reminder,
 | 
						|
            "SELECT
 | 
						|
             reminders.attachment,
 | 
						|
             reminders.attachment_name,
 | 
						|
             reminders.content,
 | 
						|
             reminders.embed_author,
 | 
						|
             reminders.embed_author_url,
 | 
						|
             reminders.embed_color,
 | 
						|
             reminders.embed_description,
 | 
						|
             reminders.embed_footer,
 | 
						|
             reminders.embed_footer_url,
 | 
						|
             reminders.embed_image_url,
 | 
						|
             reminders.embed_thumbnail_url,
 | 
						|
             reminders.embed_title,
 | 
						|
             reminders.embed_fields,
 | 
						|
             reminders.enabled,
 | 
						|
             reminders.expires,
 | 
						|
             reminders.interval_seconds,
 | 
						|
             reminders.interval_days,
 | 
						|
             reminders.interval_months,
 | 
						|
             reminders.name,
 | 
						|
             reminders.tts,
 | 
						|
             reminders.uid,
 | 
						|
             reminders.utc_time
 | 
						|
            FROM reminders
 | 
						|
            WHERE uid = ?",
 | 
						|
            new_uid
 | 
						|
        )
 | 
						|
        .fetch_one(transaction.executor())
 | 
						|
        .await
 | 
						|
        .map(|r| Ok(json!(r)))
 | 
						|
        .unwrap_or_else(|e| {
 | 
						|
            warn!("Failed to complete SQL query: {:?}", e);
 | 
						|
 | 
						|
            Err(json!({"error": "Could not load reminder"}))
 | 
						|
        }),
 | 
						|
 | 
						|
        Err(e) => {
 | 
						|
            warn!("Error in `create_reminder`: Could not execute query: {:?}", e);
 | 
						|
 | 
						|
            Err(json!({"error": "Unknown error"}))
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 |