feat(discord): Added /balance {give,set} commands

- Also, you cannot /answer to unpublished quest
- Also, changed /scoreboard to print name instead of mentioning
- Also, made --config an option, defaulting to "cfg/config.toml"
This commit is contained in:
Alexey 2025-12-15 13:26:37 +03:00
commit 4ba57b925a
6 changed files with 111 additions and 10 deletions

View file

@ -6,5 +6,5 @@ use clap::Parser;
pub struct Cli {
/// Path to config.toml
#[arg(long, short)]
pub config: PathBuf,
pub config: Option<PathBuf>,
}

View file

@ -1,14 +1,21 @@
use poise::serenity_prelude::{Mentionable, UserId};
use poise::serenity_prelude::UserId;
use squad_quest::{SquadObject, account::Account, map::Map};
use crate::{Context, Error};
use crate::{Context, Error, account::fetch_or_init_account};
fn account_balance_string(account: &Account, map: &Map) -> String {
async fn account_balance_string(ctx: &Context<'_>, account: &Account, map: &Map) -> String {
let rooms_value = account_rooms_value(account, map);
let full_balance = account_full_balance(account, map);
format!("\n{account}: **{full_balance}** points (**{balance}** on balance \
let account_id = account_user_id(&account);
let Ok(user) = account_id
.to_user(ctx)
.await else {
return String::new();
};
let name = user.display_name();
format!("\n{name}: **{full_balance}** points (**{balance}** on balance \
+ **{rooms_value}** unlocked rooms networth)",
account = account_user_id(&account).mention(),
balance = account.balance,
)
}
@ -57,7 +64,7 @@ pub async fn scoreboard(
for account in accounts {
let user_id = account_user_id(&account);
let mut str = account_balance_string(&account, &map);
let mut str = account_balance_string(&ctx, &account, &map).await;
if user_id == this_user {
str = format!("__{str}__ << You");
}
@ -69,3 +76,82 @@ pub async fn scoreboard(
Ok(())
}
#[poise::command(
prefix_command,
slash_command,
guild_only,
subcommands("give", "set"),
)]
pub async fn balance(
_ctx: Context<'_>,
) -> Result<(), Error> {
Ok(())
}
#[poise::command(
prefix_command,
slash_command,
guild_only,
)]
pub async fn give(
ctx: Context<'_>,
who: UserId,
amount: u32,
) -> Result<(), Error> {
let config = &ctx.data().config;
let mut accounts = config.load_accounts();
let user_id = format!("{}", ctx.author().id.get());
let mut user_account = fetch_or_init_account(config, user_id);
let who_id = format!("{}", who.get());
let Some(other_account) = accounts.iter_mut().find(|a| a.id == who_id ) else {
return Err(Error::AccountNotFound);
};
if user_account.balance < amount {
return Err(Error::InsufficientFunds(amount));
}
user_account.balance -= amount;
other_account.balance += amount;
let accounts_path = config.full_accounts_path();
user_account.save(accounts_path.clone())?;
other_account.save(accounts_path)?;
let reply_string = format!("Given money to user.\n\
Your new balance: {} points.", user_account.balance);
ctx.reply(reply_string).await?;
Ok(())
}
#[poise::command(
prefix_command,
slash_command,
guild_only,
required_permissions = "ADMINISTRATOR",
)]
pub async fn set(
ctx: Context<'_>,
who: UserId,
amount: u32,
) -> Result<(), Error> {
let mut accounts = ctx.data().config.load_accounts();
let who_id = format!("{}", who.get());
let Some(account) = accounts.iter_mut().find(|a| a.id == who_id ) else {
return Err(Error::AccountNotFound);
};
account.balance = amount;
let accounts_path = ctx.data().config.full_accounts_path();
account.save(accounts_path)?;
let reply_string = format!("Set user balance to {amount}.");
ctx.reply(reply_string).await?;
Ok(())
}

View file

@ -28,7 +28,9 @@ pub async fn answer(
}
let quests = ctx.data().config.load_quests();
let Some(quest) = quests.iter().find(|q| q.id == quest_id) else {
let Some(quest) = quests.iter()
.filter(|q| q.public)
.find(|q| q.id == quest_id) else {
return Err(Error::QuestNotFound(quest_id));
};

View file

@ -39,7 +39,9 @@ fn make_quest_message_content(quest: &Quest) -> String {
#[poise::command(
prefix_command,
slash_command,
guild_only,
subcommands("list", "create", "update", "publish", "delete"),
required_permissions = "ADMINISTRATOR",
)]
pub async fn quest(
_ctx: Context<'_>,
@ -50,6 +52,8 @@ pub async fn quest(
#[poise::command(
prefix_command,
slash_command,
guild_only,
required_permissions = "ADMINISTRATOR",
)]
pub async fn list(
ctx: Context<'_>,

View file

@ -13,6 +13,8 @@ pub enum Error {
BothChannelAndUser,
SerenityError(serenity::Error),
SquadQuestError(squad_quest::error::Error),
AccountNotFound,
InsufficientFunds(u32),
}
impl From<serenity::Error> for Error {
@ -38,6 +40,8 @@ impl Display for Error {
Self::BothChannelAndUser => write!(f, "both channel and user was specified"),
Self::SerenityError(_) => write!(f, "discord interaction error"),
Self::SquadQuestError(_) => write!(f, "internal logic error"),
Self::AccountNotFound => write!(f, "account not found"),
Self::InsufficientFunds(amount) => write!(f, "account does not have {amount} points"),
}
}
}
@ -50,7 +54,9 @@ impl std::error::Error for Error {
Self::QuestIsCompleted(_) |
Self::NoContent |
Self::NoChannelOrUser |
Self::BothChannelAndUser => None,
Self::BothChannelAndUser |
Self::AccountNotFound |
Self::InsufficientFunds(_) => None,
Self::SerenityError(error) => Some(error),
Self::SquadQuestError(error) => Some(error),
}

View file

@ -13,6 +13,8 @@ mod config;
mod account;
mod error;
const CONFIG_PATH: &str = "cfg/config.toml";
#[derive(Debug)]
struct Data {
pub config: Config,
@ -25,7 +27,7 @@ async fn main() {
dotenv().unwrap();
let cli = cli::Cli::parse();
let config = Config::load(cli.config.clone());
let config = Config::load(cli.config.clone().unwrap_or(CONFIG_PATH.into()));
let discord = config.discord_impl().unwrap_or_else(|_| {
config.init_impl().unwrap();
config.discord_impl().unwrap()
@ -44,6 +46,7 @@ async fn main() {
commands::answer::answer(),
commands::social::social(),
commands::account::scoreboard(),
commands::account::balance(),
],
..Default::default()
})