Expand description

A wrapper over Twilight that’s designed to be convenient to use, without relying on callbacks and mostly following Twilight patterns while making your life easier

Concise Startup

use sparkle_convenience::Bot;
Bot::new(
    "forgot to leak my token".to_owned(),
    Intents::GUILD_MESSAGES,
    EventTypeFlags::INTERACTION_CREATE,
)
.await?;

Yes that’s really it… Bot has all the things you’d need from Twilight

Interaction Handling

use sparkle_convenience::{
    error::conversion::IntoError,
    interaction::extract::{InteractionDataExt, InteractionExt},
    reply::Reply,
    Bot,
};

let handle = bot.interaction_handle(&interaction);
match interaction.name().ok()? {
    "pay_respects" => {
        handle.defer(true).await?;
        // More on error handling below
        handle.check_permissions(Permissions::MANAGE_GUILD)?;
        // Say this is a user command
        let _very_respected_user = interaction.data.ok()?.command().ok()?.target_id.ok()?;
        // There are similar methods for autocomplete and modal responses
        handle
            .followup(
                Reply::new()
                    .ephemeral()
                    .content("You have -1 respect now".to_owned()),
            )
            .await?;
    }
    _ => {}
}

Error Handling

User-Facing Errors

use sparkle_convenience::{
    error::{conversion::IntoError, ErrorExt, UserError},
    http::message::CreateMessageExt,
    prettify::Prettify,
    reply::Reply,
};

async fn wave(
    client: &Client,
    channel_id: Id<ChannelMarker>,
    message_id: Id<MessageMarker>,
) -> Result<()> {
    client
        .create_reaction(
            channel_id,
            message_id,
            &RequestReactionType::Unicode { name: "👋" },
        )
        .await?;
    Ok(())
}

async fn handle_message(client: &Client, message: Message) -> Result<()> {
    if let Err(mut err) = wave(client, message.channel_id, message.id).await {
        // For example if the message was already deleted (probably by some moderation bot..)
        if err.ignore() {
            return Ok(());
        }

        // Not needed in interactions thanks to `InteractionHandle::check_permissions`
        err.with_permissions(Permissions::READ_MESSAGE_HISTORY | Permissions::ADD_REACTIONS);

        client
            .create_message(message.channel_id)
            .with_reply(&err_reply(&err))?
            .execute_ignore_permissions()
            .await?;

        // `CustomError` is for your own errors
        if let Some(err) = err.internal::<CustomError>() {
            return Err(err);
        }
    }

    Ok(())
}

// Returns a reply that you can conveniently use in messages, interactions, even webhooks
fn err_reply(err: &anyhow::Error) -> Reply {
    let message = if let Some(UserError::MissingPermissions(permissions)) = err.user() {
        format!(
            "Give me those sweet permissions:\n{}",
            permissions.unwrap().prettify() // Also provided by this crate
        )
    } else {
        "Uh oh...".to_owned()
    };
    Reply::new().ephemeral().content(message)
}

Internal Errors

use sparkle_convenience::Bot;

bot.set_logging_channel(Id::new(123)).await?;
bot.set_logging_file("log.txt".to_owned());
if let Err(err) = handle_event().await {
    // Executes a webhook in the channel
    // (error message is in an attachment so don't worry if it's too long)
    // And appends the error to the file
    bot.log(format!("{err:?}")).await;
};

DMs

use sparkle_convenience::http::HttpExt;

client
    .dm_user(user_id)
    .await?
    .content("This bot is brought to you by Skillshare")?
    .await?;

Modules

Convenient error handling
Making HTTP requests conveniently
Convenient interaction handling
Formatting types into user-readable pretty strings

Structs

All data required to make a bot run