feat(ticket): close reason modal + reopen/transcript/delete panel (forum+channel)
This commit is contained in:
parent
d7669a6864
commit
9c0b2624e2
3 changed files with 170 additions and 8 deletions
|
|
@ -91,7 +91,9 @@ impl EventHandler for Handler {
|
||||||
serenity::all::Interaction::Modal(modal) => {
|
serenity::all::Interaction::Modal(modal) => {
|
||||||
let m = modal.clone();
|
let m = modal.clone();
|
||||||
if m.data.custom_id.starts_with("ticket_modal:") {
|
if m.data.custom_id.starts_with("ticket_modal:") {
|
||||||
crate::feature::ticket::handler::on_modal(ctx, m).await;
|
crate::feature::ticket::handler::interaction::on_modal(ctx, m).await;
|
||||||
|
} else if m.data.custom_id == "ticket_close_modal" {
|
||||||
|
crate::feature::ticket::handler::claim::handle_close_modal(ctx, m).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
|
|
||||||
|
|
@ -79,23 +79,133 @@ pub async fn handle_claim(ctx: Context, interaction: serenity::all::ComponentInt
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn handle_close(ctx: Context, interaction: serenity::all::ComponentInteraction) {
|
pub async fn handle_close(ctx: Context, interaction: serenity::all::ComponentInteraction) {
|
||||||
|
let modal =
|
||||||
|
serenity::all::CreateModal::new("ticket_close_modal", "Close ticket").components(vec![
|
||||||
|
serenity::all::CreateActionRow::InputText(
|
||||||
|
serenity::all::CreateInputText::new(
|
||||||
|
serenity::all::InputTextStyle::Paragraph,
|
||||||
|
"Reason",
|
||||||
|
"close_reason",
|
||||||
|
)
|
||||||
|
.placeholder("Why are you closing? (10-500 chars)")
|
||||||
|
.min_length(10)
|
||||||
|
.max_length(500)
|
||||||
|
.required(true),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
let _ = interaction
|
||||||
|
.create_response(&ctx.http, serenity::all::CreateInteractionResponse::Modal(modal))
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
pub async fn handle_close_modal(ctx: Context, interaction: serenity::all::ModalInteraction) {
|
||||||
|
if interaction.data.custom_id != "ticket_close_modal" {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let reason = interaction
|
||||||
|
.data
|
||||||
|
.components
|
||||||
|
.iter()
|
||||||
|
.flat_map(|r| &r.components)
|
||||||
|
.find_map(|c| match c {
|
||||||
|
serenity::all::ActionRowComponent::InputText(it) if it.custom_id == "close_reason" => {
|
||||||
|
it.value.clone()
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
if reason.trim().len() < 10 {
|
||||||
|
let _ = interaction
|
||||||
|
.create_response(
|
||||||
|
&ctx.http,
|
||||||
|
serenity::all::CreateInteractionResponse::Message(
|
||||||
|
serenity::all::CreateInteractionResponseMessage::new()
|
||||||
|
.content("Reason 10-500 chars required.")
|
||||||
|
.ephemeral(true),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
return;
|
||||||
|
}
|
||||||
let channel_id = interaction.channel_id;
|
let channel_id = interaction.channel_id;
|
||||||
let user = interaction.user.clone();
|
let user = interaction.user.clone();
|
||||||
ephemeral(&ctx, &interaction, "Closing ticket... transcript will be saved.".into()).await;
|
let _ = interaction
|
||||||
|
.create_response(
|
||||||
|
&ctx.http,
|
||||||
|
serenity::all::CreateInteractionResponse::Defer(
|
||||||
|
serenity::all::CreateInteractionResponseMessage::new().ephemeral(true),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
let transcript = service::fetch_transcript(&ctx.http, channel_id).await;
|
let transcript = service::fetch_transcript(&ctx.http, channel_id).await;
|
||||||
let haste_url = service::upload_hastebin(&transcript).await;
|
let haste_url = service::upload_hastebin(&transcript).await;
|
||||||
service::deliver_transcript(
|
service::deliver_transcript(
|
||||||
&ctx.http,
|
&ctx.http,
|
||||||
channel_id,
|
channel_id,
|
||||||
user.id.get(),
|
user.id.get(),
|
||||||
transcript.clone(),
|
format!("{transcript}\n\nClose reason: {reason}"),
|
||||||
haste_url.clone(),
|
haste_url.clone(),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
if let Some(pool) = service::pool() {
|
if let Some(pool) = service::pool() {
|
||||||
service::cleanup_ticket(pool, channel_id.get()).await;
|
service::cleanup_ticket(pool, channel_id.get()).await;
|
||||||
}
|
}
|
||||||
tokio::time::sleep(std::time::Duration::from_secs(2)).await; // SAFETY: small delay to let log flush
|
let control_row = serenity::all::CreateActionRow::Buttons(vec![
|
||||||
|
serenity::all::CreateButton::new("ticket_reopen")
|
||||||
|
.label("Reopen")
|
||||||
|
.style(serenity::all::ButtonStyle::Secondary),
|
||||||
|
serenity::all::CreateButton::new("ticket_transcript")
|
||||||
|
.label("Save")
|
||||||
|
.style(serenity::all::ButtonStyle::Secondary),
|
||||||
|
serenity::all::CreateButton::new("ticket_delete")
|
||||||
|
.label("Delete")
|
||||||
|
.style(serenity::all::ButtonStyle::Danger),
|
||||||
|
]);
|
||||||
|
let embed = CreateEmbed::new()
|
||||||
|
.title("Ticket closed")
|
||||||
|
.description(format!("Closed by <@{}>\nReason: {reason}", user.id))
|
||||||
|
.color(0x00ED_4245)
|
||||||
|
.footer(serenity::all::CreateEmbedFooter::new("Control panel below"));
|
||||||
|
if let Some(url) = haste_url.clone() {
|
||||||
|
let link_row = serenity::all::CreateActionRow::Buttons(vec![
|
||||||
|
serenity::all::CreateButton::new_link(url).label("View transcript"),
|
||||||
|
]);
|
||||||
|
let _ = channel_id
|
||||||
|
.send_message(
|
||||||
|
&ctx.http,
|
||||||
|
CreateMessage::new().embed(embed).components(vec![control_row, link_row]),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
} else {
|
||||||
|
let _ = channel_id
|
||||||
|
.send_message(
|
||||||
|
&ctx.http,
|
||||||
|
CreateMessage::new().embed(embed).components(vec![control_row]),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
let _ = ChannelId::new(super::TICKET_LOGS)
|
||||||
|
.send_message(
|
||||||
|
&ctx.http,
|
||||||
|
CreateMessage::new().embed(
|
||||||
|
CreateEmbed::new()
|
||||||
|
.title("Ticket closed")
|
||||||
|
.description(format!(
|
||||||
|
"<@{}> closed <#{}>\nReason: {reason}",
|
||||||
|
user.id, channel_id
|
||||||
|
))
|
||||||
|
.color(0x00ED_4245),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||||
|
let _ = interaction
|
||||||
|
.create_followup(
|
||||||
|
&ctx.http,
|
||||||
|
serenity::all::CreateInteractionResponseFollowup::new()
|
||||||
|
.content(format!("Ticket <#{channel_id}> closed."))
|
||||||
|
.ephemeral(true),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
let is_thread = ctx
|
let is_thread = ctx
|
||||||
.http
|
.http
|
||||||
.get_channel(channel_id)
|
.get_channel(channel_id)
|
||||||
|
|
@ -106,9 +216,56 @@ pub async fn handle_close(ctx: Context, interaction: serenity::all::ComponentInt
|
||||||
let _ = channel_id
|
let _ = channel_id
|
||||||
.edit_thread(&ctx.http, serenity::all::EditThread::new().archived(true).locked(true))
|
.edit_thread(&ctx.http, serenity::all::EditThread::new().archived(true).locked(true))
|
||||||
.await;
|
.await;
|
||||||
info!(channel=%channel_id, thread=true, "ticket thread closed (archived)");
|
}
|
||||||
|
info!(channel=%channel_id, closer=%user.id, "ticket closed with reason");
|
||||||
|
}
|
||||||
|
pub async fn handle_reopen(ctx: Context, interaction: serenity::all::ComponentInteraction) {
|
||||||
|
let channel_id = interaction.channel_id;
|
||||||
|
let is_thread = ctx
|
||||||
|
.http
|
||||||
|
.get_channel(channel_id)
|
||||||
|
.await
|
||||||
|
.map(|c| matches!(c, serenity::all::Channel::Guild(gc) if gc.thread_metadata.is_some()))
|
||||||
|
.unwrap_or(false);
|
||||||
|
if is_thread {
|
||||||
|
let _ = channel_id
|
||||||
|
.edit_thread(&ctx.http, serenity::all::EditThread::new().archived(false).locked(false))
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
ephemeral(&ctx, &interaction, "Ticket reopened.".into()).await;
|
||||||
|
let _ = channel_id
|
||||||
|
.send_message(
|
||||||
|
&ctx.http,
|
||||||
|
CreateMessage::new().embed(
|
||||||
|
CreateEmbed::new()
|
||||||
|
.title("Ticket reopened")
|
||||||
|
.description(format!("Reopened by <@{}>", interaction.user.id))
|
||||||
|
.color(0x0057_F387),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
pub async fn handle_transcript(ctx: Context, interaction: serenity::all::ComponentInteraction) {
|
||||||
|
let channel_id = interaction.channel_id;
|
||||||
|
ephemeral(&ctx, &interaction, "Generating transcript...".into()).await;
|
||||||
|
let transcript = service::fetch_transcript(&ctx.http, channel_id).await;
|
||||||
|
if let Some(url) = service::upload_hastebin(&transcript).await {
|
||||||
|
let _ = channel_id
|
||||||
|
.send_message(&ctx.http, CreateMessage::new().content(format!("Transcript: {url}")))
|
||||||
|
.await;
|
||||||
} else {
|
} else {
|
||||||
|
let data =
|
||||||
|
serenity::all::CreateAttachment::bytes(transcript.into_bytes(), "transcript.txt");
|
||||||
|
let _ = channel_id.send_message(&ctx.http, CreateMessage::new().add_file(data)).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub async fn handle_delete(ctx: Context, interaction: serenity::all::ComponentInteraction) {
|
||||||
|
let channel_id = interaction.channel_id;
|
||||||
|
ephemeral(&ctx, &interaction, "Deleting ticket...".into()).await;
|
||||||
|
if let Some(pool) = service::pool() {
|
||||||
|
service::cleanup_ticket(pool, channel_id.get()).await;
|
||||||
|
}
|
||||||
|
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||||
let _ = channel_id.delete(&ctx.http).await;
|
let _ = channel_id.delete(&ctx.http).await;
|
||||||
info!(channel=%channel_id, "ticket closed");
|
info!(channel=%channel_id, "ticket deleted via panel");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ pub mod claim;
|
||||||
pub mod create;
|
pub mod create;
|
||||||
pub mod interaction;
|
pub mod interaction;
|
||||||
|
|
||||||
pub use interaction::{on_modal, on_select};
|
pub use interaction::on_select;
|
||||||
|
|
||||||
use serenity::all::{
|
use serenity::all::{
|
||||||
ChannelId, Context, CreateActionRow, CreateEmbed, CreateSelectMenu, CreateSelectMenuKind,
|
ChannelId, Context, CreateActionRow, CreateEmbed, CreateSelectMenu, CreateSelectMenuKind,
|
||||||
|
|
@ -127,6 +127,9 @@ pub async fn on_button(ctx: Context, interaction: serenity::all::ComponentIntera
|
||||||
"ticket_general" | "ticket_bug" => create::handle_create_legacy(ctx, interaction).await,
|
"ticket_general" | "ticket_bug" => create::handle_create_legacy(ctx, interaction).await,
|
||||||
"ticket_claim" => claim::handle_claim(ctx, interaction).await,
|
"ticket_claim" => claim::handle_claim(ctx, interaction).await,
|
||||||
"ticket_close" => claim::handle_close(ctx, interaction).await,
|
"ticket_close" => claim::handle_close(ctx, interaction).await,
|
||||||
|
"ticket_reopen" => claim::handle_reopen(ctx, interaction).await,
|
||||||
|
"ticket_transcript" => claim::handle_transcript(ctx, interaction).await,
|
||||||
|
"ticket_delete" => claim::handle_delete(ctx, interaction).await,
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue