Skip to content

Commit

Permalink
Rework of builtins::help() (#161)
Browse files Browse the repository at this point in the history
* Rework of builtins::help()

* Use find_command instead of duplicating code

* Fix env variable `set` -> `export` for Unix

* Fix unneeded mut

* cargo fmt

---------

Co-authored-by: kangalioo <jannik.a.schaper@web.de>
  • Loading branch information
2 people authored and GnomedDev committed Nov 26, 2023
1 parent 6c08cfb commit d38d226
Show file tree
Hide file tree
Showing 6 changed files with 482 additions and 62 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ env_logger = "0.10.1"
fluent = "0.16.0"
intl-memoizer = "0.5.1"
fluent-syntax = "0.11"
rand = "0.8.5"

[features]
default = ["serenity/rustls_backend", "cache", "chrono", "handle_panics"]
Expand Down
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
Here is a curated list of examples that thrive to demonstrate the capabilities of `poise` and how its features are meant to be used.

You must set the following environment variables:

- `DISCORD_TOKEN`: your application's token

Application ID and owner ID don't have to be set, since they are requested from Discord on startup
Expand Down
305 changes: 305 additions & 0 deletions examples/help_generation/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,305 @@
use poise::{samples::HelpConfiguration, serenity_prelude as serenity};
use rand::Rng;

struct Data {} // User data, which is stored and accessible in all command invocations
type Error = Box<dyn std::error::Error + Send + Sync>;
type Context<'a> = poise::Context<'a, Data, Error>;

const FRUIT: &[&str] = &["🍎", "🍌", "🍊", "🍉", "🍇", "🍓"];
const VEGETABLES: &[&str] = &["🥕", "🥦", "🥬", "🥒", "🌽", "🥔"];
const MEAT: &[&str] = &["🥩", "🍗", "🍖", "🥓", "🍔", "🍕"];
const DAIRY: &[&str] = &["🥛", "🧀", "🍦", "🍨", "🍩", "🍪"];
const FOOD: &[&str] = &[
"🍎", "🍌", "🍊", "🍉", "🍇", "🍓", "🥕", "🥦", "🥬", "🥒", "🌽", "🥔", "🥩", "🍗", "🍖", "🥓",
"🍔", "🍕", "🥛", "🧀", "🍦", "🍨", "🍩", "🍪",
];

fn ninetynine_bottles() -> String {
let mut bottles = String::new();
for i in (95..100).rev() {
bottles.push_str(&format!(
"{0} bottles of beer on the wall, {0} bottles of beer!\n",
i
));
bottles.push_str(&format!(
"Take one down, pass it around, {0} bottles of beer on the wall!\n",
i - 1
));
}
bottles += "That's quite enough to demonstrate this function!";
bottles
}

#[poise::command(
slash_command,
prefix_command,
category = "Vegan",
help_text_fn = "ninetynine_bottles"
)]
async fn beer(ctx: Context<'_>) -> Result<(), Error> {
ctx.say("🍺").await?;
Ok(())
}

/// Respond with a random fruit
///
/// Subcommands can be used to get a specific fruit
#[poise::command(
slash_command,
prefix_command,
subcommands(
"apple",
"banana",
"orange",
"watermelon",
"grape",
"strawberry",
"help"
),
category = "Vegan"
)]
async fn fruit(ctx: Context<'_>) -> Result<(), Error> {
let response = FRUIT[rand::thread_rng().gen_range(0..FRUIT.len())];
ctx.say(response).await?;
Ok(())
}

/// Respond with an apple
#[poise::command(slash_command, prefix_command, subcommands("red", "green"))]
async fn apple(ctx: Context<'_>) -> Result<(), Error> {
ctx.say("🍎").await?;
Ok(())
}

/// Respond with a red apple
#[poise::command(slash_command, prefix_command)]
async fn red(ctx: Context<'_>) -> Result<(), Error> {
ctx.say("🍎").await?;
Ok(())
}

/// Respond with a green apple
#[poise::command(slash_command, prefix_command)]
async fn green(ctx: Context<'_>) -> Result<(), Error> {
ctx.say("🍏").await?;
Ok(())
}

/// Respond with a banana
#[poise::command(slash_command, prefix_command)]
async fn banana(ctx: Context<'_>) -> Result<(), Error> {
ctx.say("🍌").await?;
Ok(())
}

/// Respond with an orange
#[poise::command(slash_command, prefix_command)]
async fn orange(ctx: Context<'_>) -> Result<(), Error> {
ctx.say("🍊").await?;
Ok(())
}

/// Respond with a watermelon
#[poise::command(slash_command, prefix_command)]
async fn watermelon(ctx: Context<'_>) -> Result<(), Error> {
ctx.say("🍉").await?;
Ok(())
}

/// Respond with a grape
#[poise::command(slash_command, prefix_command)]
async fn grape(ctx: Context<'_>) -> Result<(), Error> {
ctx.say("🍇").await?;
Ok(())
}

/// Respond with a strawberry
#[poise::command(slash_command, prefix_command)]
async fn strawberry(ctx: Context<'_>) -> Result<(), Error> {
ctx.say("🍓").await?;
Ok(())
}

/// Respond with a random vegetable
#[poise::command(slash_command, prefix_command, category = "Vegan")]
async fn vegetable(ctx: Context<'_>) -> Result<(), Error> {
let response = VEGETABLES[rand::thread_rng().gen_range(0..VEGETABLES.len())];
ctx.say(response).await?;
Ok(())
}

/// Respond with a random meat
#[poise::command(slash_command, prefix_command, category = "Other")]
async fn meat(ctx: Context<'_>) -> Result<(), Error> {
let response = MEAT[rand::thread_rng().gen_range(0..MEAT.len())];
ctx.say(response).await?;
Ok(())
}

/// Respond with a random dairy product
#[poise::command(slash_command, prefix_command, category = "Other")]
async fn dairy(ctx: Context<'_>) -> Result<(), Error> {
let response = DAIRY[rand::thread_rng().gen_range(0..DAIRY.len())];
ctx.say(response).await?;
Ok(())
}

/// Give a user some random food
#[poise::command(context_menu_command = "Give food")]
async fn context_food(
ctx: Context<'_>,
#[description = "User to give food to"] user: serenity::User,
) -> Result<(), Error> {
let response = format!(
"<@{}>: {}",
user.id,
FOOD[rand::thread_rng().gen_range(0..FOOD.len())]
);

ctx.say(response).await?;
Ok(())
}

/// Give a user some random fruit
#[poise::command(
slash_command,
context_menu_command = "Give fruit",
category = "Context menu but also slash/prefix"
)]
async fn context_fruit(
ctx: Context<'_>,
#[description = "User to give fruit to"] user: serenity::User,
) -> Result<(), Error> {
let response = format!(
"<@{}>: {}",
user.id,
FRUIT[rand::thread_rng().gen_range(0..FRUIT.len())]
);

ctx.say(response).await?;
Ok(())
}

/// Give a user some random vegetable
#[poise::command(
prefix_command,
context_menu_command = "Give vegetable",
category = "Context menu but also slash/prefix"
)]
async fn context_vegetable(
ctx: Context<'_>,
#[description = "User to give vegetable to"] user: serenity::User,
) -> Result<(), Error> {
let response = format!(
"<@{}>: {}",
user.id,
VEGETABLES[rand::thread_rng().gen_range(0..VEGETABLES.len())]
);

ctx.say(response).await?;
Ok(())
}

/// Give a user some random meat
#[poise::command(
prefix_command,
slash_command,
context_menu_command = "Give meat",
category = "Context menu but also slash/prefix"
)]
async fn context_meat(
ctx: Context<'_>,
#[description = "User to give meat to"] user: serenity::User,
) -> Result<(), Error> {
let response = format!(
"<@{}>: {}",
user.id,
MEAT[rand::thread_rng().gen_range(0..MEAT.len())]
);

ctx.say(response).await?;
Ok(())
}

/// React to a message with random food
#[poise::command(slash_command, context_menu_command = "React with food", ephemeral)]
async fn food_react(
ctx: Context<'_>,
#[description = "Message to react to (enter a link or ID)"] msg: serenity::Message,
) -> Result<(), Error> {
let reaction = FOOD[rand::thread_rng().gen_range(0..FOOD.len())].to_string();
msg.react(ctx, serenity::ReactionType::Unicode(reaction))
.await?;
ctx.say("Reacted!").await?;
Ok(())
}

/// Show help message
#[poise::command(prefix_command, track_edits, category = "Utility")]
async fn help(
ctx: Context<'_>,
#[description = "Command to get help for"]
#[rest]
mut command: Option<String>,
) -> Result<(), Error> {
// This makes it possible to just make `help` a subcommand of any command
// `/fruit help` turns into `/help fruit`
// `/fruit help apple` turns into `/help fruit apple`
if ctx.invoked_command_name() != "help" {
command = match command {
Some(c) => Some(format!("{} {}", ctx.invoked_command_name(), c)),
None => Some(ctx.invoked_command_name().to_string()),
};
}
let extra_text_at_bottom = "\
Type `?help command` for more info on a command.
You can edit your `?help` message to the bot and the bot will edit its response.";

let config = HelpConfiguration {
show_subcommands: true,
show_context_menu_commands: true,
ephemeral: true,
extra_text_at_bottom,

..Default::default()
};
poise::builtins::help(ctx, command.as_deref(), config).await?;
Ok(())
}

#[tokio::main]
async fn main() {
let framework = poise::Framework::builder()
.options(poise::FrameworkOptions {
commands: vec![
fruit(),
vegetable(),
beer(),
meat(),
dairy(),
help(),
context_food(),
context_fruit(),
context_vegetable(),
context_meat(),
food_react(),
],
prefix_options: poise::PrefixFrameworkOptions {
prefix: Some("?".into()),
..Default::default()
},
..Default::default()
})
.token(std::env::var("DISCORD_TOKEN").expect("missing DISCORD_TOKEN"))
.intents(
serenity::GatewayIntents::non_privileged() | serenity::GatewayIntents::MESSAGE_CONTENT,
)
.setup(|ctx, _ready, framework| {
Box::pin(async move {
poise::builtins::register_globally(ctx, &framework.options().commands).await?;
Ok(Data {})
})
});

framework.run().await.unwrap();
}

0 comments on commit d38d226

Please sign in to comment.