jude 6f223b1bc2 Remove usages of FIND_IN_SET
FIND_IN_SET doesn't make use of indexes
2024-10-10 19:06:56 +01:00

460 lines
18 KiB
Rust

pub(crate) mod pager;
use std::io::Cursor;
use base64::{engine::general_purpose, Engine};
use chrono_tz::Tz;
use log::warn;
use poise::{
serenity_prelude as serenity,
serenity_prelude::{
builder::CreateEmbed, ComponentInteraction, ComponentInteractionDataKind, Context,
CreateEmbedFooter, CreateInteractionResponse, CreateInteractionResponseMessage,
},
};
use rmp_serde::Serializer;
use serde::{Deserialize, Serialize};
use crate::{
commands::{
command_macro::list_macro::{max_macro_page, show_macro_page},
delete::{max_delete_page, show_delete_page},
todo::{max_todo_page, show_todo_page},
},
component_models::pager::{DelPager, LookPager, MacroPager, Pager, TodoPager},
consts::{EMBED_DESCRIPTION_MAX_LENGTH, THEME_COLOR},
models::reminder::Reminder,
utils::reply_to_interaction_response_message,
Data,
};
#[derive(Deserialize, Serialize)]
#[serde(tag = "type")]
#[repr(u8)]
pub enum ComponentDataModel {
LookPager(LookPager),
DelPager(DelPager),
TodoPager(TodoPager),
DelSelector(DelSelector),
TodoSelector(TodoSelector),
MacroPager(MacroPager),
UndoReminder(UndoReminder),
}
impl ComponentDataModel {
pub fn to_custom_id(&self) -> String {
let mut buf = Vec::new();
self.serialize(&mut Serializer::new(&mut buf)).unwrap();
general_purpose::STANDARD.encode(buf)
}
pub fn from_custom_id(data: &String) -> Self {
let buf = general_purpose::STANDARD
.decode(data)
.map_err(|e| format!("Could not decode `custom_id' {}: {:?}", data, e))
.unwrap();
let cur = Cursor::new(buf);
rmp_serde::from_read(cur).unwrap()
}
pub async fn act(&self, ctx: &Context, data: &Data, component: &ComponentInteraction) {
match self {
ComponentDataModel::LookPager(pager) => {
let flags = pager.flags;
let channel_id = {
let channel_opt = component.channel_id.to_channel_cached(&ctx.cache);
if let Some(channel) = channel_opt {
if Some(channel.guild_id) == component.guild_id {
flags.channel_id.unwrap_or(component.channel_id)
} else {
component.channel_id
}
} else {
component.channel_id
}
};
let reminders = Reminder::from_channel(&data.database, channel_id, &flags).await;
let pages = reminders
.iter()
.map(|reminder| reminder.display(&flags, &pager.timezone))
.fold(0, |t, r| t + r.len())
.div_ceil(EMBED_DESCRIPTION_MAX_LENGTH);
let channel_name =
channel_id.to_channel_cached(&ctx.cache).map(|channel| channel.name.clone());
let next_page = pager.next_page(pages);
let mut char_count = 0;
let mut skip_char_count = 0;
let display = reminders
.iter()
.map(|reminder| reminder.display(&flags, &pager.timezone))
.skip_while(|p| {
skip_char_count += p.len();
skip_char_count < EMBED_DESCRIPTION_MAX_LENGTH * next_page
})
.take_while(|p| {
char_count += p.len();
char_count < EMBED_DESCRIPTION_MAX_LENGTH
})
.collect::<Vec<String>>()
.join("");
let embed = CreateEmbed::default()
.title(format!(
"Reminders{}",
channel_name.map_or(String::new(), |n| format!(" on #{}", n))
))
.description(display)
.footer(CreateEmbedFooter::new(format!("Page {} of {}", next_page + 1, pages)))
.color(*THEME_COLOR);
let _ = component
.create_response(
&ctx,
CreateInteractionResponse::UpdateMessage(
CreateInteractionResponseMessage::new()
.embed(embed)
.components(vec![pager.create_button_row(pages)]),
),
)
.await;
}
ComponentDataModel::DelPager(pager) => {
let reminders = Reminder::from_guild(
&ctx,
&data.database,
component.guild_id,
component.user.id,
)
.await;
let max_pages = max_delete_page(&reminders, &pager.timezone);
let resp = show_delete_page(&reminders, pager.next_page(max_pages), pager.timezone);
let _ = component
.create_response(
&ctx,
CreateInteractionResponse::UpdateMessage(
reply_to_interaction_response_message(resp),
),
)
.await;
}
ComponentDataModel::DelSelector(selector) => {
if let ComponentInteractionDataKind::StringSelect { values } = &component.data.kind
{
let placeholder = vec!["?"; values.len()].join(",");
let sql = format!(
"UPDATE reminders SET `status` = 'deleted' WHERE id IN ({placeholder})"
);
let mut query = sqlx::query(&sql);
for id in values {
query = query.bind(id);
}
query.execute(&data.database).await.unwrap();
let reminders = Reminder::from_guild(
&ctx,
&data.database,
component.guild_id,
component.user.id,
)
.await;
let resp = show_delete_page(&reminders, selector.page, selector.timezone);
let _ = component
.create_response(
&ctx,
CreateInteractionResponse::UpdateMessage(
reply_to_interaction_response_message(resp),
),
)
.await;
}
}
ComponentDataModel::TodoPager(pager) => {
if Some(component.user.id.get()) == pager.user_id || pager.user_id.is_none() {
let values = if let Some(uid) = pager.user_id {
sqlx::query!(
"
SELECT todos.id, value FROM todos
WHERE user_id = ?
",
uid,
)
.fetch_all(&data.database)
.await
.unwrap()
.iter()
.map(|row| (row.id as usize, row.value.clone()))
.collect::<Vec<(usize, String)>>()
} else if let Some(cid) = pager.channel_id {
sqlx::query!(
"
SELECT todos.id, value FROM todos
INNER JOIN channels ON todos.channel_id = channels.id
WHERE channels.channel = ?
",
cid,
)
.fetch_all(&data.database)
.await
.unwrap()
.iter()
.map(|row| (row.id as usize, row.value.clone()))
.collect::<Vec<(usize, String)>>()
} else {
sqlx::query!(
"
SELECT todos.id, value FROM todos
INNER JOIN guilds ON todos.guild_id = guilds.id
WHERE guilds.guild = ?
",
pager.guild_id,
)
.fetch_all(&data.database)
.await
.unwrap()
.iter()
.map(|row| (row.id as usize, row.value.clone()))
.collect::<Vec<(usize, String)>>()
};
let max_pages = max_todo_page(&values);
let resp = show_todo_page(
&values,
pager.next_page(max_pages),
pager.user_id,
pager.channel_id,
pager.guild_id,
);
let _ = component
.create_response(
&ctx,
CreateInteractionResponse::UpdateMessage(
reply_to_interaction_response_message(resp),
),
)
.await;
} else {
let _ = component
.create_response(
&ctx,
CreateInteractionResponse::Message(
CreateInteractionResponseMessage::new()
.ephemeral(true)
.content("Only the user who performed the command can use these components")
)
)
.await;
}
}
ComponentDataModel::TodoSelector(selector) => {
if Some(component.user.id.get()) == selector.user_id || selector.user_id.is_none() {
if let ComponentInteractionDataKind::StringSelect { values } =
&component.data.kind
{
let placeholder = vec!["?"; values.len()].join(",");
let sql = format!("DELETE FROM todos WHERE id IN ({placeholder})");
let mut query = sqlx::query(&sql);
for id in values {
query = query.bind(id);
}
query.execute(&data.database).await.unwrap();
let values = if let Some(uid) = selector.user_id {
sqlx::query!(
"
SELECT todos.id, value FROM todos
WHERE user_id = ?
",
uid,
)
.fetch_all(&data.database)
.await
.unwrap()
.iter()
.map(|row| (row.id as usize, row.value.clone()))
.collect::<Vec<(usize, String)>>()
} else if let Some(cid) = selector.channel_id {
sqlx::query!(
"
SELECT todos.id, value FROM todos
INNER JOIN channels ON todos.channel_id = channels.id
WHERE channels.channel = ?
",
cid,
)
.fetch_all(&data.database)
.await
.unwrap()
.iter()
.map(|row| (row.id as usize, row.value.clone()))
.collect::<Vec<(usize, String)>>()
} else {
sqlx::query!(
"
SELECT todos.id, value FROM todos
INNER JOIN guilds ON todos.guild_id = guilds.id
WHERE guilds.guild = ?
",
selector.guild_id,
)
.fetch_all(&data.database)
.await
.unwrap()
.iter()
.map(|row| (row.id as usize, row.value.clone()))
.collect::<Vec<(usize, String)>>()
};
let resp = show_todo_page(
&values,
selector.page,
selector.user_id,
selector.channel_id,
selector.guild_id,
);
let _ = component
.create_response(
&ctx,
CreateInteractionResponse::UpdateMessage(
reply_to_interaction_response_message(resp),
),
)
.await;
}
} else {
let _ = component
.create_response(
&ctx,
CreateInteractionResponse::Message(
CreateInteractionResponseMessage::new()
.ephemeral(true)
.content("Only the user who performed the command can use these components")
)
)
.await;
}
}
ComponentDataModel::MacroPager(pager) => {
let macros = data.command_macros(component.guild_id.unwrap()).await.unwrap();
let max_page = max_macro_page(&macros);
let page = pager.next_page(max_page);
let resp = show_macro_page(&macros, page);
let _ = component
.create_response(
&ctx,
CreateInteractionResponse::UpdateMessage(
reply_to_interaction_response_message(resp),
),
)
.await;
}
ComponentDataModel::UndoReminder(undo_reminder) => {
if component.user.id == undo_reminder.user_id {
let reminder =
Reminder::from_id(&data.database, undo_reminder.reminder_id).await;
if let Some(reminder) = reminder {
match reminder.delete(&data.database).await {
Ok(()) => {
let _ = component
.create_response(
&ctx,
CreateInteractionResponse::UpdateMessage(
CreateInteractionResponseMessage::new().embed(
CreateEmbed::new()
.title("Reminder Canceled")
.description("This reminder has been canceled.")
.color(*THEME_COLOR),
),
),
)
.await;
}
Err(e) => {
warn!("Error canceling reminder: {:?}", e);
let _ = component
.create_response(
&ctx,
CreateInteractionResponse::Message(
CreateInteractionResponseMessage::new().content(
"An error occurred trying to cancel this reminder.",
).ephemeral(true),
),
)
.await;
}
}
} else {
let _ = component
.create_response(
&ctx,
CreateInteractionResponse::Message(
CreateInteractionResponseMessage::new().content(
"The reminder could not be canceled. It may have already been deleted.",
).ephemeral(true),
),
)
.await;
}
} else {
let _ = component
.create_response(
&ctx,
CreateInteractionResponse::Message(
CreateInteractionResponseMessage::new()
.content("Only the user who performed the command can use these components")
.ephemeral(true),
),
)
.await;
}
}
}
}
}
#[derive(Serialize, Deserialize)]
pub struct DelSelector {
pub page: usize,
pub timezone: Tz,
}
#[derive(Serialize, Deserialize)]
pub struct TodoSelector {
pub page: usize,
pub user_id: Option<u64>,
pub channel_id: Option<u64>,
pub guild_id: Option<u64>,
}
#[derive(Serialize, Deserialize)]
pub struct UndoReminder {
pub user_id: serenity::UserId,
pub reminder_id: u32,
}