refactor: make affy main cargo project
This commit is contained in:
parent
fd81b64262
commit
f9d31ce114
4 changed files with 12 additions and 14 deletions
162
src/commands.rs
Normal file
162
src/commands.rs
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
use crate::{Context, Error};
|
||||
use affluences_api::HourBlock;
|
||||
use chrono::{Duration, NaiveDate};
|
||||
use uuid::{uuid, Uuid};
|
||||
|
||||
const STERRE_BIB_ID: Uuid = uuid!("4737e57a-ee05-4f7b-901a-7bb541eeb297");
|
||||
const TIME_FORMAT: &'static str = "%H:%M";
|
||||
|
||||
/// Show this help menu
|
||||
#[poise::command(prefix_command, track_edits, slash_command)]
|
||||
pub async fn help(
|
||||
ctx: Context<'_>,
|
||||
#[description = "Specific command to show help about"]
|
||||
#[autocomplete = "poise::builtins::autocomplete_command"]
|
||||
command: Option<String>,
|
||||
) -> Result<(), Error> {
|
||||
poise::builtins::help(
|
||||
ctx,
|
||||
command.as_deref(),
|
||||
poise::builtins::HelpConfiguration {
|
||||
extra_text_at_bottom: "This is an example bot made to showcase features of my custom Discord bot framework",
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Vote for something
|
||||
///
|
||||
/// Enter `~vote pumpkin` to vote for pumpkins
|
||||
#[poise::command(prefix_command, slash_command)]
|
||||
pub async fn vote(
|
||||
ctx: Context<'_>,
|
||||
#[description = "What to vote for"] choice: String,
|
||||
) -> Result<(), Error> {
|
||||
// Lock the Mutex in a block {} so the Mutex isn't locked across an await point
|
||||
let num_votes = {
|
||||
let mut hash_map = ctx.data().votes.lock().unwrap();
|
||||
let num_votes = hash_map.entry(choice.clone()).or_default();
|
||||
*num_votes += 1;
|
||||
*num_votes
|
||||
};
|
||||
|
||||
let response = format!("Successfully voted for {choice}. {choice} now has {num_votes} votes!");
|
||||
ctx.say(response).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Retrieve number of votes
|
||||
///
|
||||
/// Retrieve the number of votes either in general, or for a specific choice:
|
||||
/// ```
|
||||
/// ~getvotes
|
||||
/// ~getvotes pumpkin
|
||||
/// ```
|
||||
#[poise::command(prefix_command, track_edits, aliases("votes"), slash_command)]
|
||||
pub async fn getvotes(
|
||||
ctx: Context<'_>,
|
||||
#[description = "Choice to retrieve votes for"] choice: Option<String>,
|
||||
) -> Result<(), Error> {
|
||||
if let Some(choice) = choice {
|
||||
let num_votes = *ctx.data().votes.lock().unwrap().get(&choice).unwrap_or(&0);
|
||||
let response = match num_votes {
|
||||
0 => format!("Nobody has voted for {} yet", choice),
|
||||
_ => format!("{} people have voted for {}", num_votes, choice),
|
||||
};
|
||||
ctx.say(response).await?;
|
||||
} else {
|
||||
let mut response = String::new();
|
||||
for (choice, num_votes) in ctx.data().votes.lock().unwrap().iter() {
|
||||
response += &format!("{}: {} votes", choice, num_votes);
|
||||
}
|
||||
|
||||
if response.is_empty() {
|
||||
response += "Nobody has voted for anything yet :(";
|
||||
}
|
||||
|
||||
ctx.say(response).await?;
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// List available timeslots for day
|
||||
#[poise::command(prefix_command, slash_command)]
|
||||
pub async fn available(ctx: Context<'_>, date: NaiveDate) -> Result<(), Error> {
|
||||
let client = &ctx.data().client;
|
||||
let resources = client.available(STERRE_BIB_ID, date, 1).await?;
|
||||
let mut fields: Vec<(String, String, bool)> = Default::default();
|
||||
|
||||
for resource in &resources {
|
||||
if resource.hours.len() == 0 {
|
||||
fields.push((
|
||||
resource.resource_name.clone(),
|
||||
"Nothing available.".to_string(),
|
||||
false,
|
||||
));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut lines: Vec<String> = Default::default();
|
||||
|
||||
let mut start_hour_opt: Option<&HourBlock> = None;
|
||||
let mut duration = Duration::seconds(0);
|
||||
|
||||
for hour in &resource.hours {
|
||||
if let Some(start_hour) = start_hour_opt {
|
||||
if hour.state == 1 {
|
||||
duration = duration + Duration::minutes(hour.granularity.into());
|
||||
} else {
|
||||
let end_hour = start_hour.hour + duration;
|
||||
lines.push(format!(
|
||||
"{} - {} ({:02}:{:02})",
|
||||
start_hour.hour.format(TIME_FORMAT),
|
||||
end_hour.format(TIME_FORMAT),
|
||||
duration.num_hours(),
|
||||
duration.num_minutes() % 60
|
||||
));
|
||||
start_hour_opt = None;
|
||||
}
|
||||
} else if hour.state == 1 {
|
||||
start_hour_opt = Some(hour);
|
||||
duration = Duration::minutes(hour.granularity.into());
|
||||
}
|
||||
}
|
||||
|
||||
// Print final entry if present
|
||||
if let Some(start_hour) = start_hour_opt {
|
||||
let end_hour = start_hour.hour + duration;
|
||||
lines.push(format!(
|
||||
"{} - {} ({:02}:{:02})",
|
||||
start_hour.hour.format(TIME_FORMAT),
|
||||
end_hour.format(TIME_FORMAT),
|
||||
duration.num_hours(),
|
||||
duration.num_minutes() % 60
|
||||
));
|
||||
}
|
||||
|
||||
fields.push((resource.resource_name.clone(), lines.join("\n"), false));
|
||||
}
|
||||
|
||||
ctx.send(|f| {
|
||||
f.embed(|e| {
|
||||
e.description(format!("Available booking dates for {}.", date))
|
||||
.fields(fields)
|
||||
})
|
||||
})
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Create a reservation
|
||||
// #[poise::command(prefix_command, slash_command)]
|
||||
// pub async fn reserve(
|
||||
// ctx: Context<'_>,
|
||||
// date: NaiveDate,
|
||||
// ) -> Result<(), Error> {
|
||||
|
||||
// }
|
||||
111
src/main.rs
Normal file
111
src/main.rs
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
mod commands;
|
||||
|
||||
use affluences_api::AffluencesClient;
|
||||
use poise::serenity_prelude as serenity;
|
||||
use std::{collections::HashMap, env::var, sync::Mutex, time::Duration};
|
||||
|
||||
// Types used by all command functions
|
||||
type Error = Box<dyn std::error::Error + Send + Sync>;
|
||||
type Context<'a> = poise::Context<'a, Data, Error>;
|
||||
|
||||
// Custom user data passed to all command functions
|
||||
pub struct Data {
|
||||
votes: Mutex<HashMap<String, u32>>,
|
||||
client: AffluencesClient,
|
||||
}
|
||||
|
||||
async fn on_error(error: poise::FrameworkError<'_, Data, Error>) {
|
||||
// This is our custom error handler
|
||||
// They are many errors that can occur, so we only handle the ones we want to customize
|
||||
// and forward the rest to the default handler
|
||||
match error {
|
||||
poise::FrameworkError::Setup { error, .. } => panic!("Failed to start bot: {:?}", error),
|
||||
poise::FrameworkError::Command { error, ctx } => {
|
||||
println!("Error in command `{}`: {:?}", ctx.command().name, error,);
|
||||
}
|
||||
error => {
|
||||
if let Err(e) = poise::builtins::on_error(error).await {
|
||||
println!("Error while handling error: {}", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// FrameworkOptions contains all of poise's configuration option in one struct
|
||||
// Every option can be omitted to use its default value
|
||||
let options = poise::FrameworkOptions {
|
||||
commands: vec![
|
||||
commands::help(),
|
||||
commands::vote(),
|
||||
commands::getvotes(),
|
||||
commands::available(),
|
||||
],
|
||||
prefix_options: poise::PrefixFrameworkOptions {
|
||||
prefix: Some("~".into()),
|
||||
edit_tracker: Some(poise::EditTracker::for_timespan(Duration::from_secs(3600))),
|
||||
additional_prefixes: vec![
|
||||
poise::Prefix::Literal("hey bot"),
|
||||
poise::Prefix::Literal("hey bot,"),
|
||||
],
|
||||
..Default::default()
|
||||
},
|
||||
/// The global error handler for all error cases that may occur
|
||||
on_error: |error| Box::pin(on_error(error)),
|
||||
/// This code is run before every command
|
||||
pre_command: |ctx| {
|
||||
Box::pin(async move {
|
||||
println!("Executing command {}...", ctx.command().qualified_name);
|
||||
})
|
||||
},
|
||||
/// This code is run after a command if it was successful (returned Ok)
|
||||
post_command: |ctx| {
|
||||
Box::pin(async move {
|
||||
println!("Executed command {}!", ctx.command().qualified_name);
|
||||
})
|
||||
},
|
||||
/// Every command invocation must pass this check to continue execution
|
||||
command_check: Some(|ctx| {
|
||||
Box::pin(async move {
|
||||
if ctx.author().id == 123456789 {
|
||||
return Ok(false);
|
||||
}
|
||||
Ok(true)
|
||||
})
|
||||
}),
|
||||
/// Enforce command checks even for owners (enforced by default)
|
||||
/// Set to true to bypass checks, which is useful for testing
|
||||
skip_checks_for_owners: false,
|
||||
event_handler: |_ctx, event, _framework, _data| {
|
||||
Box::pin(async move {
|
||||
println!("Got an event in event handler: {:?}", event.name());
|
||||
Ok(())
|
||||
})
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
poise::Framework::builder()
|
||||
.token(
|
||||
var("DISCORD_TOKEN")
|
||||
.expect("Missing `DISCORD_TOKEN` env var, see README for more information."),
|
||||
)
|
||||
.setup(move |ctx, _ready, framework| {
|
||||
Box::pin(async move {
|
||||
println!("Logged in as {}", _ready.user.name);
|
||||
poise::builtins::register_globally(ctx, &framework.options().commands).await?;
|
||||
Ok(Data {
|
||||
votes: Mutex::new(HashMap::new()),
|
||||
client: AffluencesClient::new(),
|
||||
})
|
||||
})
|
||||
})
|
||||
.options(options)
|
||||
.intents(
|
||||
serenity::GatewayIntents::non_privileged() | serenity::GatewayIntents::MESSAGE_CONTENT,
|
||||
)
|
||||
.run()
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue