Skip to content

Commit

Permalink
Merge pull request #40 from caphosra/release/v3.1.4
Browse files Browse the repository at this point in the history
Release v3.1.4
  • Loading branch information
caphosra authored Jan 20, 2024
2 parents 521a589 + 6a2d67f commit 3911519
Show file tree
Hide file tree
Showing 11 changed files with 226 additions and 310 deletions.
10 changes: 5 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
[package]
name = "cthulhu_bot"
version = "3.1.3"
authors = ["Akihisa Yagi <capra314cabra@gmail.com>"]
version = "3.1.4"
authors = ["Akihisa Yagi <caphosra@gmail.com>"]
edition = "2018"
description = "An unofficial Discord bot which helps you to play Cthulhu TRPG. Please note that Chaosium Inc. owns the copyright of Cthulhu TRPG."
repository = "https://github.com/capra314cabra/cthulhu_bot"
repository = "https://github.com/caphosra/cthulhu_bot"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

Expand All @@ -16,8 +16,8 @@ rand = "0.8.5"
regex = "1.5.4"
serde = { version = "1.0.195", features=["derive"] }
serde_json = "1.0.111"
serenity = { version = "0.11", default-features = false, features = ["client", "gateway", "rustls_backend", "model", "unstable_discord_api"] }
sqlx = { version = "0.6", features = [ "runtime-tokio-native-tls" , "postgres" ] }
serenity = { version = "0.12.0", default-features = false, features = ["client", "gateway", "rustls_backend", "model", "unstable_discord_api"] }
sqlx = { version = "0.7.3", features = [ "runtime-tokio-native-tls" , "postgres" ] }
tokio = { version = "1.30", features = ["rt-multi-thread"] }
cmd_macro = { path = "cmd_macro" }
chrono = "0.4"
22 changes: 1 addition & 21 deletions cmd_macro/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use proc_macro::TokenStream;
use quote::{quote, ToTokens};
use regex::Regex;
use syn::{parse_macro_input, FnArg, ImplItem, ImplItemMethod, ItemImpl, Stmt};
use syn::{parse_macro_input, FnArg, ImplItem, ImplItemMethod, ItemImpl};

/// Finds a function from impl.
fn find_function<'l>(item_impl: &'l mut ItemImpl, fn_name: &str) -> Option<&'l mut ImplItemMethod> {
Expand Down Expand Up @@ -67,26 +67,6 @@ pub fn naming(_attr: TokenStream, item: TokenStream) -> TokenStream {

let name: proc_macro2::TokenStream = name.to_lowercase().parse().unwrap();

match find_function(&mut item_impl, "register") {
Some(method) => {
let arg_name = match method.sig.inputs.last().unwrap() {
FnArg::Receiver(_) => panic!("The last argument must not be a receiver."),
FnArg::Typed(typed) => typed.pat.to_token_stream(),
};

let stmt: TokenStream = (quote! {
#arg_name.name(#name);
})
.into();

method
.block
.stmts
.insert(0, parse_macro_input!(stmt as Stmt));
}
None => panic!("A function `register` should be implemented."),
}

let name_function: TokenStream = (quote! {
fn name(&self) -> &str {
#name
Expand Down
44 changes: 23 additions & 21 deletions src/commands/choose.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
use anyhow::Result;
use rand::Rng;
use serenity::builder::CreateApplicationCommand;
use serenity::model::application::command::CommandOptionType;
use serenity::model::application::interaction::application_command::ApplicationCommandInteraction;
use serenity::builder::{CreateCommand, CreateCommandOption, CreateEmbed};
use serenity::model::application::{CommandInteraction, CommandOptionType};
use serenity::prelude::{Context, Mutex};

use crate::commands::{BotCommand, CommandStatus, InteractionUtil, SendEmbed};
Expand All @@ -14,22 +13,23 @@ pub struct ChooseCommand;
#[db_required(false)]
#[serenity::async_trait]
impl BotCommand for ChooseCommand {
fn register(&self, command: &mut CreateApplicationCommand) {
command
fn create(&self) -> CreateCommand {
CreateCommand::new(self.name())
.description("Makes a random choice. | 与えられたものからランダムに選択をします.")
.create_option(|option| {
option
.name("choices")
.kind(CommandOptionType::String)
.description("Comma-separated choices (ex. A,B,C) | カンマで区切られた選択肢 (例: A,B,C)")
.required(true)
});
.add_option(
CreateCommandOption::new(
CommandOptionType::String,
"choices",
"Comma-separated choices (ex. A,B,C) | カンマで区切られた選択肢 (例: A,B,C)",
)
.required(true),
)
}

async fn execute(
&self,
ctx: &Context,
interaction: &ApplicationCommandInteraction,
interaction: &CommandInteraction,
) -> Result<CommandStatus> {
let choices: Vec<&str> = interaction
.get_string_option("choices".into())
Expand All @@ -42,14 +42,16 @@ impl BotCommand for ChooseCommand {
let selected = rand::thread_rng().gen_range(0..choices.len());

interaction
.send_embed(ctx, |embed| {
embed.title(format!("{}'s choice", author));
embed.field(
format!("**{}**", choices[selected]),
format!("From {}", choices.join(",")),
false,
)
})
.send_embed(
ctx,
CreateEmbed::new()
.title(format!("{}'s choice", author))
.field(
format!("**{}**", choices[selected]),
format!("From {}", choices.join(",")),
false,
),
)
.await?;

Ok(CommandStatus::Ok)
Expand Down
36 changes: 16 additions & 20 deletions src/commands/create_sheet.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use anyhow::Result;
use once_cell::sync::Lazy;
use serenity::builder::CreateApplicationCommand;
use serenity::model::application::interaction::application_command::ApplicationCommandInteraction;
use serenity::builder::{CreateCommand, CreateEmbed};
use serenity::model::application::CommandInteraction;
use serenity::prelude::{Context, Mutex};

use crate::commands::{AsString, BotCommand, CommandStatus, InteractionUtil, SendEmbed};
Expand Down Expand Up @@ -57,33 +57,29 @@ static STATUSES: Lazy<Vec<Status>> = Lazy::new(|| {
#[db_required(false)]
#[serenity::async_trait]
impl BotCommand for CSCommand {
fn register(&self, command: &mut CreateApplicationCommand) {
command.description("Creates a character sheet. | キャラクターシートを作成します.");
fn create(&self) -> CreateCommand {
CreateCommand::new(self.name())
.description("Creates a character sheet. | キャラクターシートを作成します.")
}

async fn execute(
&self,
ctx: &Context,
interaction: &ApplicationCommandInteraction,
interaction: &CommandInteraction,
) -> Result<CommandStatus> {
let author = interaction.get_nickname();

interaction
.send_embed(ctx, |embed| {
embed.title(format!("{}'s character", author));
let embed = CreateEmbed::new().title(format!("{}'s character", author));
let embed = STATUSES.iter().fold(embed, |embed, status| {
let result = d20::roll_dice(status.roll).unwrap();
embed.field(
format!("{} {}", status.name, result.total),
result.as_string(),
true,
)
});

for status in STATUSES.iter() {
let result = d20::roll_dice(status.roll).unwrap();
embed.field(
format!("{} {}", status.name, result.total),
result.as_string(),
true,
);
}

embed
})
.await?;
interaction.send_embed(ctx, embed).await?;

Ok(CommandStatus::Ok)
}
Expand Down
96 changes: 45 additions & 51 deletions src/commands/mod.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
use anyhow::Result;
use d20::Roll;
use once_cell::sync::Lazy;
use serenity::builder::{CreateApplicationCommand, CreateEmbed};
use serenity::model::application::command::Command;
use serenity::model::application::interaction::application_command::ApplicationCommandInteraction;
use serenity::builder::{
CreateCommand, CreateEmbed, CreateInteractionResponse, CreateInteractionResponseMessage,
};
use serenity::model::application::{Command, CommandInteraction};
use serenity::model::colour::Colour;
use serenity::prelude::{Context, Mutex};
use serenity::utils::Color;

use crate::commands::choose::ChooseCommand;
use crate::commands::create_sheet::CSCommand;
Expand All @@ -28,7 +29,7 @@ pub enum CommandStatus {
#[serenity::async_trait]
pub trait BotCommand {
/// Registers a command to Discord.
fn register(&self, command: &mut CreateApplicationCommand);
fn create(&self) -> CreateCommand;

/// Gets a name of the command.
fn name(&self) -> &str;
Expand All @@ -40,7 +41,7 @@ pub trait BotCommand {
async fn execute(
&self,
ctx: &Context,
interaction: &ApplicationCommandInteraction,
interaction: &CommandInteraction,
data: &Mutex<SizedBotDatabase>,
) -> Result<CommandStatus>;
}
Expand All @@ -67,27 +68,22 @@ pub struct BotCommandManager;
impl BotCommandManager {
/// Registers all commands to Discord.
pub async fn register_all(ctx: &Context, db_available: bool) -> Result<()> {
Command::set_global_application_commands(ctx, |builder| {
let commands = REGISTERED_COMMANDS
.iter()
.filter_map(|command| {
if db_available || !command.use_db() {
let mut builder = CreateApplicationCommand::default();
command.register(&mut builder);

tokio::spawn(async move {
log!(LOG, "Registered /{}.", command.name());
});

Some(builder)
} else {
None
}
})
.collect();
builder.set_application_commands(commands)
})
.await?;
let commands = REGISTERED_COMMANDS
.iter()
.filter_map(|command| {
if db_available || !command.use_db() {
tokio::spawn(async move {
log!(LOG, "Registering /{}.", command.name());
});

Some(command.create())
} else {
None
}
})
.collect();

Command::set_global_commands(ctx, commands).await?;

log!(LOG, "Registered all commands.");

Expand All @@ -97,7 +93,7 @@ impl BotCommandManager {
/// Executes a command.
pub async fn run_command(
ctx: &Context,
interaction: &ApplicationCommandInteraction,
interaction: &CommandInteraction,
data: &Mutex<SizedBotDatabase>,
) -> Result<()> {
let mut command_executed = false;
Expand Down Expand Up @@ -136,16 +132,17 @@ impl BotCommandManager {
/// This method cannot be used to report an internal server error.
async fn reply_error(
ctx: &Context,
interaction: &ApplicationCommandInteraction,
interaction: &CommandInteraction,
error: String,
) -> Result<()> {
interaction
.send_embed(ctx, |embed| {
embed.title("ERROR");
embed.field("Message", error, false);
embed.color(Color::RED);
embed
})
.send_embed(
ctx,
CreateEmbed::default()
.title("ERROR")
.field("Message", error, false)
.colour(Colour::RED),
)
.await?;

Ok(())
Expand All @@ -164,7 +161,7 @@ pub trait InteractionUtil {
fn get_int_option(&self, name: String) -> Option<i32>;
}

impl InteractionUtil for ApplicationCommandInteraction {
impl InteractionUtil for CommandInteraction {
fn get_nickname(&self) -> String {
match &self.member {
Some(member) => member.display_name().to_string(),
Expand All @@ -177,37 +174,34 @@ impl InteractionUtil for ApplicationCommandInteraction {
.options
.iter()
.find(|option| option.name == name)
.map(|option| option.value.as_ref().unwrap().as_str().unwrap())
.map(|option| option.value.as_str().unwrap())
}

fn get_int_option(&self, name: String) -> Option<i32> {
self.data
.options
.iter()
.find(|option| option.name == name)
.map(|option| option.value.as_ref().unwrap().as_i64().unwrap() as i32)
.map(|option| option.value.as_i64().unwrap() as i32)
}
}

/// An extension for `ApplicationCommandInteraction` to send an embed content easily.
#[serenity::async_trait]
pub trait SendEmbed<F, 'l>
where
F: FnOnce(&mut CreateEmbed) -> &mut CreateEmbed + Send + 'l,
{
pub trait SendEmbed<'l> {
/// Sends an embed to the user.
async fn send_embed(&'l self, ctx: &Context, f: F) -> Result<()>;
async fn send_embed(&'l self, ctx: &Context, embed: CreateEmbed) -> Result<()>;
}

#[serenity::async_trait]
impl<F, 'l> SendEmbed<F, 'l> for ApplicationCommandInteraction
where
F: (FnOnce(&mut CreateEmbed) -> &mut CreateEmbed) + Send + 'l,
{
async fn send_embed(&'l self, ctx: &Context, f: F) -> Result<()> {
self.create_interaction_response(&ctx, |res| {
res.interaction_response_data(|res| res.embed(f))
})
impl<'l> SendEmbed<'l> for CommandInteraction {
async fn send_embed(&'l self, ctx: &Context, embed: CreateEmbed) -> Result<()> {
self.create_response(
&ctx,
CreateInteractionResponse::Message(
CreateInteractionResponseMessage::default().add_embed(embed),
),
)
.await?;
Ok(())
}
Expand Down
Loading

0 comments on commit 3911519

Please sign in to comment.