use crate::serenity_prelude as serenity;
#[doc(hidden)]
pub fn find_modal_text(
data: &mut serenity::ModalInteractionData,
custom_id: &str,
) -> Option<String> {
for row in &mut data.components {
let text = match row.components.get_mut(0) {
Some(serenity::ActionRowComponent::InputText(text)) => text,
Some(_) => {
tracing::warn!("unexpected non input text component in modal response");
continue;
}
None => {
tracing::warn!("empty action row in modal response");
continue;
}
};
if text.custom_id == custom_id {
return match std::mem::take(&mut text.value) {
Some(val) if val.is_empty() => None,
Some(val) => Some(val),
None => None,
};
}
}
tracing::warn!(
"{} not found in modal response (expected at least blank string)",
custom_id
);
None
}
async fn execute_modal_generic<
M: Modal,
F: std::future::Future<Output = Result<(), serenity::Error>>,
>(
ctx: &serenity::Context,
create_interaction_response: impl FnOnce(serenity::CreateInteractionResponse) -> F,
modal_custom_id: String,
defaults: Option<M>,
timeout: Option<std::time::Duration>,
) -> Result<Option<M>, serenity::Error> {
create_interaction_response(M::create(defaults, modal_custom_id.clone())).await?;
let response = serenity::collector::ModalInteractionCollector::new(&ctx.shard)
.filter(move |d| d.data.custom_id == modal_custom_id)
.timeout(timeout.unwrap_or(std::time::Duration::from_secs(3600)))
.await;
let response = match response {
Some(x) => x,
None => return Ok(None),
};
response
.create_response(ctx, serenity::CreateInteractionResponse::Acknowledge)
.await?;
Ok(Some(
M::parse(response.data.clone()).map_err(serenity::Error::Other)?,
))
}
pub async fn execute_modal<U: Send + Sync, E, M: Modal>(
ctx: crate::ApplicationContext<'_, U, E>,
defaults: Option<M>,
timeout: Option<std::time::Duration>,
) -> Result<Option<M>, serenity::Error> {
let interaction = ctx.interaction;
let response = execute_modal_generic(
ctx.serenity_context,
|resp| interaction.create_response(ctx, resp),
interaction.id.to_string(),
defaults,
timeout,
)
.await?;
ctx.has_sent_initial_response
.store(true, std::sync::atomic::Ordering::SeqCst);
Ok(response)
}
pub async fn execute_modal_on_component_interaction<M: Modal>(
ctx: impl AsRef<serenity::Context>,
interaction: serenity::ComponentInteraction,
defaults: Option<M>,
timeout: Option<std::time::Duration>,
) -> Result<Option<M>, serenity::Error> {
execute_modal_generic(
ctx.as_ref(),
|resp| interaction.create_response(ctx.as_ref(), resp),
interaction.id.to_string(),
defaults,
timeout,
)
.await
}
#[async_trait::async_trait]
pub trait Modal: Sized {
fn create(defaults: Option<Self>, custom_id: String) -> serenity::CreateInteractionResponse;
fn parse(data: serenity::ModalInteractionData) -> Result<Self, &'static str>;
async fn execute<U: Send + Sync, E>(
ctx: crate::ApplicationContext<'_, U, E>,
) -> Result<Option<Self>, serenity::Error> {
execute_modal(ctx, None::<Self>, None).await
}
async fn execute_with_defaults<U: Send + Sync, E>(
ctx: crate::ApplicationContext<'_, U, E>,
defaults: Self,
) -> Result<Option<Self>, serenity::Error> {
execute_modal(ctx, Some(defaults), None).await
}
}