3 Commits

Author SHA1 Message Date
a9edcec43c Deduplicate dashboard frontend code 2025-06-24 19:56:45 +01:00
cc5f6d9d55 Bump version 2025-06-18 22:13:05 +01:00
761d545496 Improve errors and wording 2025-06-18 22:08:32 +01:00
8 changed files with 126 additions and 38 deletions

4
Cargo.lock generated
View File

@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo. # This file is automatically @generated by Cargo.
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 4
[[package]] [[package]]
name = "addr2line" name = "addr2line"
@ -2614,7 +2614,7 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]] [[package]]
name = "reminder-rs" name = "reminder-rs"
version = "1.7.37" version = "1.7.38"
dependencies = [ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"chrono", "chrono",

View File

@ -1,6 +1,6 @@
[package] [package]
name = "reminder-rs" name = "reminder-rs"
version = "1.7.37" version = "1.7.38"
authors = ["Jude Southworth <judesouthworth@pm.me>"] authors = ["Jude Southworth <judesouthworth@pm.me>"]
edition = "2021" edition = "2021"
license = "AGPL-3.0 only" license = "AGPL-3.0 only"

View File

@ -75,8 +75,8 @@ Please select a unique name for your macro.",
CreateEmbed::new() CreateEmbed::new()
.title("Macro Recording Started") .title("Macro Recording Started")
.description( .description(
"Run up to 5 commands, or type `/macro finish` to stop at any point. "Run up to 5 commands to record in this macro. Use `/macro finish` to stop recording at any point.
Any commands ran as part of recording will be inconsequential", Any commands performed during recording won't take any actual action- they are only captured for the macro.",
) )
.color(*THEME_COLOR), .color(*THEME_COLOR),
), ),

View File

@ -1,4 +1,6 @@
use poise::{CommandInteractionType, CreateReply}; use crate::consts::THEME_COLOR;
use poise::{serenity_prelude::CreateEmbed, CommandInteractionType, CreateReply};
use serenity::builder::CreateEmbedFooter;
use crate::{consts::MACRO_MAX_COMMANDS, models::command_macro::RecordedCommand, Context, Error}; use crate::{consts::MACRO_MAX_COMMANDS, models::command_macro::RecordedCommand, Context, Error};
@ -18,7 +20,18 @@ async fn macro_check(ctx: Context<'_>) -> bool {
.send( .send(
CreateReply::default() CreateReply::default()
.ephemeral(true) .ephemeral(true)
.content(format!("{} commands already recorded. Please use `/macro finish` to end recording.", MACRO_MAX_COMMANDS)) .embed(CreateEmbed::new()
.title("💾 Currently recording macro")
.description(
format!("{} commands already recorded. Please use `/macro finish` to end recording.", MACRO_MAX_COMMANDS),
)
.footer(
CreateEmbedFooter::new(
"Any commands performed during recording won't take any actual action- they are only captured for the macro"
)
)
.color(*THEME_COLOR),
),
) )
.await; .await;
} else { } else {
@ -28,9 +41,19 @@ async fn macro_check(ctx: Context<'_>) -> bool {
let _ = ctx let _ = ctx
.send( .send(
CreateReply::default() CreateReply::default().ephemeral(true).embed(
.ephemeral(true) CreateEmbed::new()
.content("Command recorded to macro"), .title("💾 Currently recording macro")
.description(
"Command recorded. Use `/macro finish` to end recording.",
)
.footer(
CreateEmbedFooter::new(
"Any commands performed during recording won't take any actual action- they are only captured for the macro"
)
)
.color(*THEME_COLOR),
),
) )
.await; .await;
} }
@ -38,8 +61,18 @@ async fn macro_check(ctx: Context<'_>) -> bool {
None => { None => {
let _ = ctx let _ = ctx
.send( .send(
CreateReply::default().ephemeral(true).content( CreateReply::default().ephemeral(true).embed(
"This command is not supported in macros yet.", CreateEmbed::new()
.title("💾 Currently recording macro")
.description(
"This command is not supported in macros, so it hasn't been recorded. Use `/macro finish` to end recording.",
)
.footer(
CreateEmbedFooter::new(
"Any commands performed during recording won't take any actual action- they are only captured for the macro"
)
)
.color(*THEME_COLOR),
), ),
) )
.await; .await;
@ -74,6 +107,7 @@ async fn check_self_permissions(ctx: Context<'_>) -> bool {
return if permissions.send_messages() return if permissions.send_messages()
&& permissions.embed_links() && permissions.embed_links()
&& manage_webhooks && manage_webhooks
&& permissions.view_channel()
{ {
true true
} else { } else {
@ -81,12 +115,13 @@ async fn check_self_permissions(ctx: Context<'_>) -> bool {
.send(CreateReply::default().content(format!( .send(CreateReply::default().content(format!(
"The bot appears to be missing some permissions: "The bot appears to be missing some permissions:
{} **View Channels**
{} **Send Message** {} **Send Message**
{} **Embed Links** {} **Embed Links**
{} **Manage Webhooks** {} **Manage Webhooks**
Please check the bot's roles, and any channel overrides. Alternatively, giving the bot Please check the bot's roles, and any channel overrides. Alternatively, giving the bot \"Administrator\" will bypass permission checks",
\"Administrator\" will bypass permission checks", if permissions.view_channel() { "" } else { "" },
if permissions.send_messages() { "" } else { "" }, if permissions.send_messages() { "" } else { "" },
if permissions.embed_links() { "" } else { "" }, if permissions.embed_links() { "" } else { "" },
if manage_webhooks { "" } else { "" }, if manage_webhooks { "" } else { "" },
@ -100,9 +135,7 @@ Please check the bot's roles, and any channel overrides. Alternatively, giving t
manage_webhooks manage_webhooks
} }
None => { None => true,
return true;
}
} }
} }

View File

@ -92,6 +92,8 @@ enum Error {
SQLx(sqlx::Error), SQLx(sqlx::Error),
#[allow(unused)] #[allow(unused)]
Serenity(serenity::Error), Serenity(serenity::Error),
#[allow(unused)]
MissingDiscordPermission(&'static str),
} }
pub async fn initialize( pub async fn initialize(

View File

@ -305,11 +305,19 @@ pub async fn edit_reminder(
Err(e) => { Err(e) => {
warn!("`create_database_channel` returned an error code: {:?}", e); warn!("`create_database_channel` returned an error code: {:?}", e);
// Provide more specific error messages based on the error type
match e {
crate::web::Error::MissingDiscordPermission(permission) => {
error.push(format!("Please ensure the bot has the \"{}\" permission in the channel", permission));
}
_ => {
error.push("Failed to configure channel for reminders. Please check the bot permissions".to_string()); error.push("Failed to configure channel for reminders. Please check the bot permissions".to_string());
} }
} }
} }
} }
}
}
None => { None => {
warn!( warn!(

View File

@ -65,7 +65,16 @@ pub async fn create_reminder(
if let Err(e) = channel { if let Err(e) = channel {
warn!("`create_database_channel` returned an error code: {:?}", e); warn!("`create_database_channel` returned an error code: {:?}", e);
return Err(json!({"error": "Failed to configure channel for reminders."})); // 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(); let channel = channel.unwrap();

View File

@ -10,6 +10,7 @@ use rocket::{
use rocket_dyn_templates::Template; use rocket_dyn_templates::Template;
use secrecy::ExposeSecret; use secrecy::ExposeSecret;
use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use serenity::http::HttpError;
use serenity::{ use serenity::{
all::CacheHttp, all::CacheHttp,
builder::CreateWebhook, builder::CreateWebhook,
@ -404,9 +405,19 @@ pub(crate) async fn create_reminder(
if let Err(e) = channel { if let Err(e) = channel {
warn!("`create_database_channel` returned an error code: {:?}", e); warn!("`create_database_channel` returned an error code: {:?}", e);
return Err( // Provide more specific error messages based on the error type
json!({"error": "Failed to configure channel for reminders. Please check the bot permissions"}), 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. Please check the bot permissions"
.to_string(),
};
return Err(json!({"error": error_msg}));
} }
let channel = channel.unwrap(); let channel = channel.unwrap();
@ -716,13 +727,36 @@ async fn create_database_channel(
match row { match row {
Ok(row) => { Ok(row) => {
let is_dm = let is_dm = channel
channel.to_channel(&ctx).await.map_err(|e| Error::Serenity(e))?.private().is_some(); .to_channel(&ctx)
.await
.map_err(|e| {
if let serenity::Error::Http(http_error) = &e {
if let HttpError::UnsuccessfulRequest(response) = http_error {
if response.error.code == 50001 {
return Error::MissingDiscordPermission("View Channel");
}
}
}
Error::Serenity(e)
})?
.private()
.is_some();
if !is_dm && (row.webhook_token.is_none() || row.webhook_id.is_none()) { if !is_dm && (row.webhook_token.is_none() || row.webhook_id.is_none()) {
let webhook = channel let webhook = channel
.create_webhook(&ctx, CreateWebhook::new("Reminder").avatar(&*DEFAULT_AVATAR)) .create_webhook(&ctx, CreateWebhook::new("Reminder").avatar(&*DEFAULT_AVATAR))
.await .await
.map_err(|e| Error::Serenity(e))?; .map_err(|e| match &e {
serenity::Error::Http(HttpError::UnsuccessfulRequest(response)) => {
match response.error.code {
50001 => Error::MissingDiscordPermission("View Channel"),
50013 => Error::MissingDiscordPermission("Manage Webhooks"),
_ => Error::Serenity(e),
}
}
_ => Error::Serenity(e),
})?;
let token = webhook.token.unwrap(); let token = webhook.token.unwrap();
@ -747,7 +781,16 @@ async fn create_database_channel(
let webhook = channel let webhook = channel
.create_webhook(&ctx, CreateWebhook::new("Reminder").avatar(&*DEFAULT_AVATAR)) .create_webhook(&ctx, CreateWebhook::new("Reminder").avatar(&*DEFAULT_AVATAR))
.await .await
.map_err(|e| Error::Serenity(e))?; .map_err(|e| match &e {
serenity::Error::Http(HttpError::UnsuccessfulRequest(response)) => {
match response.error.code {
50001 => Error::MissingDiscordPermission("View Channel"),
50013 => Error::MissingDiscordPermission("Manage Webhooks"),
_ => Error::Serenity(e),
}
}
_ => Error::Serenity(e),
})?;
let token = webhook.token.unwrap(); let token = webhook.token.unwrap();
@ -806,22 +849,15 @@ pub async fn todos_redirect(id: &str) -> Redirect {
#[get("/")] #[get("/")]
pub async fn dashboard_home(cookies: &CookieJar<'_>) -> DashboardPage { pub async fn dashboard_home(cookies: &CookieJar<'_>) -> DashboardPage {
if cookies.get_private("userid").is_some() { render_dashboard(cookies).await
match NamedFile::open(Path::new(path!("static/index.html"))).await {
Ok(f) => DashboardPage::Ok(f),
Err(e) => {
warn!("Couldn't render dashboard: {:?}", e);
DashboardPage::NotConfigured(internal_server_error().await)
}
}
} else {
DashboardPage::Unauthorised(Redirect::to("/login/discord"))
}
} }
#[get("/<_..>")] #[get("/<_..>")]
pub async fn dashboard(cookies: &CookieJar<'_>) -> DashboardPage { pub async fn dashboard(cookies: &CookieJar<'_>) -> DashboardPage {
render_dashboard(cookies).await
}
async fn render_dashboard(cookies: &CookieJar<'_>) -> DashboardPage {
if cookies.get_private("userid").is_some() { if cookies.get_private("userid").is_some() {
match NamedFile::open(Path::new(path!("static/index.html"))).await { match NamedFile::open(Path::new(path!("static/index.html"))).await {
Ok(f) => DashboardPage::Ok(f), Ok(f) => DashboardPage::Ok(f),