feat(discord)!: Added string formatter
- Added string formatter - Added Strings struct for passing strings from file - Refactored /info and /quest * to use formatter BREAKING CHANGE: Changed DiscordConfig fields
This commit is contained in:
parent
b6ea2d8958
commit
aec4ef8339
7 changed files with 386 additions and 84 deletions
|
|
@ -1,4 +1,5 @@
|
||||||
use squad_quest::{account::Account, config::Config};
|
use poise::serenity_prelude::UserId;
|
||||||
|
use squad_quest::{account::Account, config::Config, map::Map};
|
||||||
|
|
||||||
pub fn fetch_or_init_account(conf: &Config, id: String) -> Account {
|
pub fn fetch_or_init_account(conf: &Config, id: String) -> Account {
|
||||||
let accounts = conf.load_accounts();
|
let accounts = conf.load_accounts();
|
||||||
|
|
@ -10,3 +11,23 @@ pub fn fetch_or_init_account(conf: &Config, id: String) -> Account {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn account_rooms_value(account: &Account, map: &Map) -> u32 {
|
||||||
|
map.room.iter().filter_map(|r| {
|
||||||
|
if account.rooms_unlocked.contains(&r.id) {
|
||||||
|
Some(r.value)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.sum()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn account_full_balance(account: &Account, map: &Map) -> u32 {
|
||||||
|
let rooms_value = account_rooms_value(account, map);
|
||||||
|
account.balance + rooms_value
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn account_user_id(account: &Account) -> UserId {
|
||||||
|
UserId::new(account.id.clone().parse::<u64>().expect("automatically inserted"))
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use poise::serenity_prelude::UserId;
|
use poise::serenity_prelude::UserId;
|
||||||
use squad_quest::{SquadObject, account::Account, map::Map};
|
use squad_quest::{SquadObject, account::Account, map::Map};
|
||||||
|
|
||||||
use crate::{Context, Error, account::fetch_or_init_account};
|
use crate::{Context, Error, account::{account_full_balance, account_rooms_value, account_user_id, fetch_or_init_account}};
|
||||||
|
|
||||||
async fn account_balance_string(ctx: &Context<'_>, 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 rooms_value = account_rooms_value(account, map);
|
||||||
|
|
@ -20,26 +20,6 @@ async fn account_balance_string(ctx: &Context<'_>, account: &Account, map: &Map)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn account_rooms_value(account: &Account, map: &Map) -> u32 {
|
|
||||||
map.room.iter().filter_map(|r| {
|
|
||||||
if account.rooms_unlocked.contains(&r.id) {
|
|
||||||
Some(r.value)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.sum()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn account_full_balance(account: &Account, map: &Map) -> u32 {
|
|
||||||
let rooms_value = account_rooms_value(account, map);
|
|
||||||
account.balance + rooms_value
|
|
||||||
}
|
|
||||||
|
|
||||||
fn account_user_id(account: &Account) -> UserId {
|
|
||||||
UserId::new(account.id.clone().parse::<u64>().expect("automatically inserted"))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[poise::command(
|
#[poise::command(
|
||||||
prefix_command,
|
prefix_command,
|
||||||
slash_command,
|
slash_command,
|
||||||
|
|
|
||||||
|
|
@ -21,12 +21,10 @@ pub async fn register(ctx: Context<'_>) -> Result<(), Error> {
|
||||||
slash_command,
|
slash_command,
|
||||||
)]
|
)]
|
||||||
pub async fn info(ctx: Context<'_>) -> Result<(), Error> {
|
pub async fn info(ctx: Context<'_>) -> Result<(), Error> {
|
||||||
let reply_string = format!("\
|
let strings = &ctx.data().strings;
|
||||||
SquadQuest version {ver}\n\
|
let formatter = strings.formatter();
|
||||||
Find the map here: {url}",
|
let reply_string = formatter.fmt(&strings.info);
|
||||||
ver = env!("CARGO_PKG_VERSION"),
|
|
||||||
url = "not implemented yet!",
|
|
||||||
);
|
|
||||||
ctx.say(reply_string).await?;
|
ctx.say(reply_string).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use std::{future, path::Path, str::FromStr};
|
use std::{future, str::FromStr};
|
||||||
|
|
||||||
use poise::serenity_prelude::{CreateMessage, EditMessage, Message, futures::StreamExt};
|
use poise::serenity_prelude::{CreateMessage, EditMessage, Message, futures::StreamExt};
|
||||||
use squad_quest::{SquadObject, quest::{Quest, QuestDifficulty}};
|
use squad_quest::{SquadObject, quest::{Quest, QuestDifficulty}};
|
||||||
|
|
@ -24,16 +24,10 @@ async fn find_quest_message(ctx: Context<'_>, id: u16) -> Result<Option<Message>
|
||||||
Ok(messages.first().cloned())
|
Ok(messages.first().cloned())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_quest_message_content(quest: &Quest) -> String {
|
fn make_quest_message_content(ctx: Context<'_>, quest: &Quest) -> String {
|
||||||
format!("### `#{id}` {name} (+{reward})\n\
|
let strings = &ctx.data().strings;
|
||||||
Difficulty: *{difficulty:?}*\n\
|
let formatter = strings.formatter().quest(quest);
|
||||||
{description}",
|
formatter.fmt(&strings.quest.message_format)
|
||||||
id = quest.id,
|
|
||||||
name = quest.name,
|
|
||||||
reward = quest.reward,
|
|
||||||
difficulty = quest.difficulty,
|
|
||||||
description = quest.description,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[poise::command(
|
#[poise::command(
|
||||||
|
|
@ -60,13 +54,12 @@ pub async fn list(
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let conf = &ctx.data().config;
|
let conf = &ctx.data().config;
|
||||||
let quests = conf.load_quests();
|
let quests = conf.load_quests();
|
||||||
let mut reply_string = format!("Listing {} quests:", quests.len());
|
let strings = &ctx.data().strings;
|
||||||
|
let mut formatter = strings.formatter().value(quests.len());
|
||||||
|
let mut reply_string = formatter.fmt(&strings.quest.list);
|
||||||
for quest in quests {
|
for quest in quests {
|
||||||
reply_string.push_str(format!("\n#{}: {}\n\tDescription: {}",
|
formatter = formatter.quest(&quest);
|
||||||
quest.id,
|
reply_string.push_str(formatter.fmt(&strings.quest.list_item).as_str());
|
||||||
quest.name,
|
|
||||||
quest.description,
|
|
||||||
).as_str());
|
|
||||||
}
|
}
|
||||||
ctx.reply(reply_string).await?;
|
ctx.reply(reply_string).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -168,7 +161,10 @@ pub async fn create(
|
||||||
let path = conf.full_quests_path();
|
let path = conf.full_quests_path();
|
||||||
|
|
||||||
quest.save(path)?;
|
quest.save(path)?;
|
||||||
let reply_string = format!("Created quest #{}", quest.id);
|
|
||||||
|
let strings = &ctx.data().strings;
|
||||||
|
let formatter = strings.formatter().quest(&quest);
|
||||||
|
let reply_string = formatter.fmt(&strings.quest.create);
|
||||||
|
|
||||||
ctx.reply(reply_string).await?;
|
ctx.reply(reply_string).await?;
|
||||||
|
|
||||||
|
|
@ -228,7 +224,6 @@ pub async fn update(
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let new_quest = Quest {
|
let new_quest = Quest {
|
||||||
id,
|
id,
|
||||||
difficulty,
|
difficulty,
|
||||||
|
|
@ -241,23 +236,25 @@ pub async fn update(
|
||||||
deadline: dead_line,
|
deadline: dead_line,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let strings = &ctx.data().strings;
|
||||||
|
let formatter = strings.formatter().quest(&new_quest);
|
||||||
|
|
||||||
if new_quest.public {
|
if new_quest.public {
|
||||||
let content = make_quest_message_content(&new_quest);
|
let content = make_quest_message_content(ctx, &new_quest);
|
||||||
let builder = EditMessage::new().content(content);
|
let builder = EditMessage::new().content(content);
|
||||||
|
|
||||||
let message = find_quest_message(ctx, id).await?;
|
let message = find_quest_message(ctx, id).await?;
|
||||||
if let Some(mut message) = message {
|
if let Some(mut message) = message {
|
||||||
message.edit(ctx, builder).await?;
|
message.edit(ctx, builder).await?;
|
||||||
} else {
|
} else {
|
||||||
let reply_string = format!("Quest #{id} is public, but its message was not found in the quest channel",
|
let reply_string = formatter.fmt(&strings.quest.message_not_found);
|
||||||
);
|
|
||||||
ctx.reply(reply_string).await?;
|
ctx.reply(reply_string).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let path = conf.full_quests_path();
|
let path = conf.full_quests_path();
|
||||||
new_quest.save(path)?;
|
new_quest.save(path)?;
|
||||||
let reply_string = format!("Updated quest #{id}");
|
let reply_string = formatter.fmt(&strings.quest.update);
|
||||||
ctx.reply(reply_string).await?;
|
ctx.reply(reply_string).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -286,7 +283,7 @@ pub async fn publish(
|
||||||
|
|
||||||
quest.public = true;
|
quest.public = true;
|
||||||
|
|
||||||
let content = make_quest_message_content(&quest);
|
let content = make_quest_message_content(ctx, &quest);
|
||||||
|
|
||||||
let builder = CreateMessage::new()
|
let builder = CreateMessage::new()
|
||||||
.content(content);
|
.content(content);
|
||||||
|
|
@ -297,19 +294,15 @@ pub async fn publish(
|
||||||
guard.quests_channel
|
guard.quests_channel
|
||||||
};
|
};
|
||||||
|
|
||||||
let message = channel.send_message(ctx, builder).await?;
|
channel.send_message(ctx, builder).await?;
|
||||||
|
|
||||||
{
|
|
||||||
let mut guard = dc.lock().expect("shouldn't be locked");
|
|
||||||
guard.quests_messages.push(message.id);
|
|
||||||
let path = ctx.data().config.full_impl_path().unwrap();
|
|
||||||
guard.save(path.parent().unwrap_or(Path::new("")).to_owned())?
|
|
||||||
};
|
|
||||||
|
|
||||||
let quests_path = ctx.data().config.full_quests_path();
|
let quests_path = ctx.data().config.full_quests_path();
|
||||||
quest.save(quests_path)?;
|
quest.save(quests_path)?;
|
||||||
|
|
||||||
let reply_string = format!("Published quest #{id}");
|
let strings = &ctx.data().strings;
|
||||||
|
let formatter = strings.formatter().quest(&quest);
|
||||||
|
|
||||||
|
let reply_string = formatter.fmt(&strings.quest.publish);
|
||||||
ctx.reply(reply_string).await?;
|
ctx.reply(reply_string).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -342,7 +335,15 @@ pub async fn delete(
|
||||||
account.save(accounts_path.clone())?;
|
account.save(accounts_path.clone())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let reply_string = format!("Successfully deleted quest #{id}");
|
let mock_quest = Quest {
|
||||||
|
id,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let strings = &ctx.data().strings;
|
||||||
|
let formatter = strings.formatter().quest(&mock_quest);
|
||||||
|
|
||||||
|
let reply_string = formatter.fmt(&strings.quest.delete);
|
||||||
ctx.reply(reply_string).await?;
|
ctx.reply(reply_string).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
|
|
@ -1,35 +1,55 @@
|
||||||
use std::{io::Write, path::{Path, PathBuf}};
|
use std::{io::Write, path::{Path, PathBuf}};
|
||||||
|
|
||||||
use poise::serenity_prelude::{ChannelId, GuildId, MessageId};
|
use poise::serenity_prelude::{ChannelId, GuildId};
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
use squad_quest::{SquadObject, config::Config, error::Error};
|
use squad_quest::{SquadObject, config::Config, error::Error};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Default, Clone, Debug)]
|
use crate::strings::Strings;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
pub struct DiscordConfig {
|
pub struct DiscordConfig {
|
||||||
pub guild: GuildId,
|
pub guild: GuildId,
|
||||||
pub quests_channel: ChannelId,
|
pub quests_channel: ChannelId,
|
||||||
pub answers_channel: ChannelId,
|
pub answers_channel: ChannelId,
|
||||||
pub quests_messages: Vec<MessageId>,
|
pub strings_path: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for DiscordConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
guild: GuildId::default(),
|
||||||
|
quests_channel: ChannelId::default(),
|
||||||
|
answers_channel: ChannelId::default(),
|
||||||
|
strings_path: "strings.toml".into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ConfigImpl {
|
pub trait ConfigImpl {
|
||||||
fn discord_impl(&self) -> Result<DiscordConfig, Error>;
|
fn discord_impl(&self) -> Result<(DiscordConfig, Strings), Error>;
|
||||||
fn init_impl(&self) -> Result<(), Error>;
|
fn init_impl(&self) -> Result<(), Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConfigImpl for Config {
|
impl ConfigImpl for Config {
|
||||||
fn discord_impl(&self) -> Result<DiscordConfig, Error> {
|
fn discord_impl(&self) -> Result<(DiscordConfig, Strings), Error> {
|
||||||
let Some(path) = &self.full_impl_path() else {
|
let Some(path) = self.full_impl_path() else {
|
||||||
return Err(Error::IsNotImplemented);
|
return Err(Error::IsNotImplemented);
|
||||||
};
|
};
|
||||||
DiscordConfig::load(path.clone())
|
let discord = DiscordConfig::load(path.clone())?;
|
||||||
|
let mut strings_path: PathBuf = path.parent().unwrap_or(Path::new("")).to_owned();
|
||||||
|
strings_path.push(discord.strings_path.clone());
|
||||||
|
let strings = Strings::load(strings_path)?;
|
||||||
|
Ok((discord, strings))
|
||||||
}
|
}
|
||||||
fn init_impl(&self) -> Result<(), Error> {
|
fn init_impl(&self) -> Result<(), Error> {
|
||||||
let Some(path) = self.full_impl_path() else {
|
let Some(path) = self.full_impl_path() else {
|
||||||
return Err(Error::IsNotImplemented);
|
return Err(Error::IsNotImplemented);
|
||||||
};
|
};
|
||||||
|
let folder = path.parent().unwrap_or(Path::new("")).to_owned();
|
||||||
let dc = DiscordConfig::default();
|
let dc = DiscordConfig::default();
|
||||||
dc.save(path.parent().unwrap_or(Path::new("")).to_owned())
|
dc.save(folder.clone())?;
|
||||||
|
let strings = Strings::default();
|
||||||
|
strings.save(folder)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -46,17 +66,8 @@ impl SquadObject for DiscordConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn delete(path: PathBuf) -> Result<(), Error> {
|
fn delete(_path: PathBuf) -> Result<(), Error> {
|
||||||
match Self::load(path.clone()) {
|
unimplemented!()
|
||||||
Ok(_) => {
|
|
||||||
if let Err(error) = std::fs::remove_file(path) {
|
|
||||||
return Err(Error::IoError(error));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
},
|
|
||||||
Err(error) => Err(error)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn save(&self, path: PathBuf) -> Result<(), Error> {
|
fn save(&self, path: PathBuf) -> Result<(), Error> {
|
||||||
|
|
|
||||||
|
|
@ -5,20 +5,23 @@ use dotenvy::dotenv;
|
||||||
use poise::serenity_prelude as serenity;
|
use poise::serenity_prelude as serenity;
|
||||||
use squad_quest::config::Config;
|
use squad_quest::config::Config;
|
||||||
|
|
||||||
use crate::{commands::error_handler, config::{ConfigImpl, DiscordConfig}, error::Error};
|
use crate::{commands::error_handler, config::{ConfigImpl, DiscordConfig}, error::Error, strings::Strings};
|
||||||
|
|
||||||
mod commands;
|
mod commands;
|
||||||
mod cli;
|
mod cli;
|
||||||
mod config;
|
mod config;
|
||||||
mod account;
|
mod account;
|
||||||
mod error;
|
mod error;
|
||||||
|
mod strings;
|
||||||
|
|
||||||
const CONFIG_PATH: &str = "cfg/config.toml";
|
const CONFIG_PATH: &str = "cfg/config.toml";
|
||||||
|
const DISCORD_TOKEN: &str = "DISCORD_TOKEN";
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Data {
|
struct Data {
|
||||||
pub config: Config,
|
pub config: Config,
|
||||||
pub discord: Arc<Mutex<DiscordConfig>>,
|
pub discord: Arc<Mutex<DiscordConfig>>,
|
||||||
|
pub strings: Strings,
|
||||||
}
|
}
|
||||||
type Context<'a> = poise::Context<'a, Data, Error>;
|
type Context<'a> = poise::Context<'a, Data, Error>;
|
||||||
|
|
||||||
|
|
@ -28,12 +31,12 @@ async fn main() {
|
||||||
|
|
||||||
let cli = cli::Cli::parse();
|
let cli = cli::Cli::parse();
|
||||||
let config = Config::load(cli.config.clone().unwrap_or(CONFIG_PATH.into()));
|
let config = Config::load(cli.config.clone().unwrap_or(CONFIG_PATH.into()));
|
||||||
let discord = config.discord_impl().unwrap_or_else(|_| {
|
let (discord, strings) = config.discord_impl().unwrap_or_else(|_| {
|
||||||
config.init_impl().unwrap();
|
config.init_impl().unwrap();
|
||||||
config.discord_impl().unwrap()
|
config.discord_impl().unwrap()
|
||||||
});
|
});
|
||||||
|
|
||||||
let token = std::env::var("DISCORD_TOKEN").expect("missing DISCORD_TOKEN");
|
let token = std::env::var(DISCORD_TOKEN).expect("missing DISCORD_TOKEN");
|
||||||
let intents = serenity::GatewayIntents::non_privileged();
|
let intents = serenity::GatewayIntents::non_privileged();
|
||||||
|
|
||||||
let framework = poise::Framework::builder()
|
let framework = poise::Framework::builder()
|
||||||
|
|
@ -60,6 +63,7 @@ async fn main() {
|
||||||
Ok(Data {
|
Ok(Data {
|
||||||
config,
|
config,
|
||||||
discord: Arc::new(Mutex::new(discord)),
|
discord: Arc::new(Mutex::new(discord)),
|
||||||
|
strings,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
287
discord/src/strings.rs
Normal file
287
discord/src/strings.rs
Normal file
|
|
@ -0,0 +1,287 @@
|
||||||
|
use std::{collections::HashMap, fmt::Display, io::Write, path::PathBuf};
|
||||||
|
|
||||||
|
use poise::serenity_prelude::{Mentionable, User};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use squad_quest::{SquadObject, account::Account, map::Map, quest::{Quest, QuestDifficulty}, error::Error};
|
||||||
|
|
||||||
|
use crate::account::{account_full_balance, account_rooms_value};
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Clone)]
|
||||||
|
pub struct StringFormatter {
|
||||||
|
tags: HashMap<String, String>,
|
||||||
|
difficulty: Difficulty,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StringFormatter {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let newline = ("{n}".to_string(), '\n'.to_string());
|
||||||
|
let version = ("{v}".to_string(), env!("CARGO_PKG_VERSION").to_string());
|
||||||
|
let new_tags = vec![ newline, version ];
|
||||||
|
|
||||||
|
Self::default().with_tags(new_tags).to_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_tags(mut self, tags: Vec<(String, String)>) -> Self {
|
||||||
|
for tag in tags {
|
||||||
|
self.tags.insert(tag.0, tag.1);
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn strings(mut self, strings: &Strings) -> Self {
|
||||||
|
self.difficulty = strings.difficulty.clone();
|
||||||
|
|
||||||
|
let url = ("{url}".to_string(), strings.url.clone());
|
||||||
|
let points = ("{pt}".to_string(), strings.points.clone());
|
||||||
|
let new_tags = vec![ url, points ];
|
||||||
|
|
||||||
|
self.with_tags(new_tags)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn quest(self, quest: &Quest) -> Self {
|
||||||
|
let id = ("{q.id}".to_string(), id(quest.id));
|
||||||
|
let difficulty = ("{q.difficulty}".to_string(), self.difficulty.as_string(&quest.difficulty));
|
||||||
|
let reward = ("{q.reward}".to_string(), self.points(quest.reward.to_string()));
|
||||||
|
let name = ("{q.name}".to_string(), quest.name.clone());
|
||||||
|
let description = ("{q.description}".to_string(), quest.description.clone());
|
||||||
|
let answer = ("{q.answer}".to_string(), quest.answer.clone());
|
||||||
|
let new_tags = vec![ id, difficulty, reward, name, description, answer ];
|
||||||
|
|
||||||
|
self.with_tags(new_tags)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn user(self, user: &User) -> Self {
|
||||||
|
let mention = ("{u.mention}".to_string(), user.mention().to_string());
|
||||||
|
let name = ("{u.name}".to_string(), user.display_name().to_string());
|
||||||
|
let new_tags = vec![ mention, name ];
|
||||||
|
|
||||||
|
self.with_tags(new_tags)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn balance(self, account: &Account, map: &Map) -> Self {
|
||||||
|
let balance = ("{b.current}".to_string(), self.points(account.balance));
|
||||||
|
let full_balance = (
|
||||||
|
"{b.full}".to_string(),
|
||||||
|
self.points(account_full_balance(account, map)),
|
||||||
|
);
|
||||||
|
let rooms_balance = (
|
||||||
|
"{b.rooms}".to_string(),
|
||||||
|
self.points(account_rooms_value(account, map)),
|
||||||
|
);
|
||||||
|
let new_tags = vec![ balance, full_balance, rooms_balance ];
|
||||||
|
|
||||||
|
self.with_tags(new_tags)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn text(self, text: impl ToString) -> Self {
|
||||||
|
let text = ("{text}".to_string(), text.to_string());
|
||||||
|
|
||||||
|
self.with_tags(vec![text])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn value(self, value: impl ToString) -> Self {
|
||||||
|
let value = ("{value}".to_string(), value.to_string());
|
||||||
|
|
||||||
|
self.with_tags(vec![value])
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn points(&self, str: impl Display) -> String {
|
||||||
|
let template = format!("{str} {pt}", pt = "{pt}");
|
||||||
|
self.fmt(&template)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fmt(&self, string: &str) -> String {
|
||||||
|
let mut formatted = string.to_string();
|
||||||
|
for (tag, replacement) in self.tags.iter() {
|
||||||
|
formatted = formatted.replace(tag, replacement);
|
||||||
|
}
|
||||||
|
formatted
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn id(str: impl Display) -> String {
|
||||||
|
format!("#{str}")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
pub struct Strings {
|
||||||
|
pub url: String,
|
||||||
|
pub points: String,
|
||||||
|
pub info: String,
|
||||||
|
pub answer: Answer,
|
||||||
|
pub difficulty: Difficulty,
|
||||||
|
pub scoreboard: Scoreboard,
|
||||||
|
pub quest: QuestStrings,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Strings {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
url: "not implemented!".to_string(),
|
||||||
|
points: "points".to_string(),
|
||||||
|
info: "SquadQuest version {v}\
|
||||||
|
{n}Find the map here: {url}".to_string(),
|
||||||
|
answer: Answer::default(),
|
||||||
|
difficulty: Difficulty::default(),
|
||||||
|
scoreboard: Scoreboard::default(),
|
||||||
|
quest: QuestStrings::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SquadObject for Strings {
|
||||||
|
fn load(path: PathBuf) -> Result<Self, Error> {
|
||||||
|
match std::fs::read_to_string(path) {
|
||||||
|
Ok(string) => {
|
||||||
|
match toml::from_str::<Self>(&string) {
|
||||||
|
Ok(object) => Ok(object),
|
||||||
|
Err(error) => Err(Error::TomlDeserializeError(error))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(error) => Err(Error::IoError(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete(_path: PathBuf) -> Result<(), Error> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn save(&self, path: PathBuf) -> Result<(), Error> {
|
||||||
|
let filename = "strings.toml".to_string();
|
||||||
|
let mut full_path = path;
|
||||||
|
full_path.push(filename);
|
||||||
|
|
||||||
|
let str = match toml::to_string_pretty(&self) {
|
||||||
|
Ok(string) => string,
|
||||||
|
Err(error) => {
|
||||||
|
return Err(Error::TomlSerializeError(error));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut file = match std::fs::File::create(full_path) {
|
||||||
|
Ok(f) => f,
|
||||||
|
Err(error) => {
|
||||||
|
return Err(Error::IoError(error));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(error) = file.write_all(str.as_bytes()) {
|
||||||
|
return Err(Error::IoError(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Strings {
|
||||||
|
pub fn formatter(&self) -> StringFormatter {
|
||||||
|
StringFormatter::new().strings(self).to_owned()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
#[serde(default)]
|
||||||
|
pub struct Answer {
|
||||||
|
pub from: String,
|
||||||
|
pub quest: String,
|
||||||
|
pub expected: String,
|
||||||
|
pub text: String,
|
||||||
|
pub attachment_notice: String,
|
||||||
|
pub accepted_by: String,
|
||||||
|
pub rejected_by: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Answer {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
from: "## From: {u.mention}{n}".to_string(),
|
||||||
|
quest: "### Quest {q.id}: {q.name}{n}".to_string(),
|
||||||
|
expected: "### Expected answer:{n}||{q.answer}||".to_string(),
|
||||||
|
text: "### Passed answer:{n}{text}".to_string(),
|
||||||
|
attachment_notice: "Passed answer has attachments.".to_string(),
|
||||||
|
accepted_by: "{text}{n}Accepted by: {u.mention}".to_string(),
|
||||||
|
rejected_by: "~~{text}~~{n}Rejected by: {u.mention}".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||||
|
#[serde(default)]
|
||||||
|
pub struct Difficulty {
|
||||||
|
pub easy: String,
|
||||||
|
pub normal: String,
|
||||||
|
pub hard: String,
|
||||||
|
pub secret: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Difficulty {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
easy: "Easy".to_string(),
|
||||||
|
normal: "Normal".to_string(),
|
||||||
|
hard: "Hard".to_string(),
|
||||||
|
secret: "Secret".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Difficulty {
|
||||||
|
pub fn as_string(&self, difficulty: &QuestDifficulty) -> String {
|
||||||
|
match difficulty {
|
||||||
|
QuestDifficulty::Easy => self.easy.clone(),
|
||||||
|
QuestDifficulty::Normal => self.normal.clone(),
|
||||||
|
QuestDifficulty::Hard => self.hard.clone(),
|
||||||
|
QuestDifficulty::Secret => self.secret.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
pub struct Scoreboard {
|
||||||
|
pub header: String,
|
||||||
|
pub line_format: String,
|
||||||
|
pub you_format: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Scoreboard {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
header: "Current scoreboard:".to_string(),
|
||||||
|
line_format: "{n}{u.name}: **{b.full}** (**{b.current}** on balance\
|
||||||
|
+ **{b.rooms}** unlocked rooms networth)".to_string(),
|
||||||
|
you_format: "__{text}__ << You".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
pub struct QuestStrings {
|
||||||
|
pub list: String,
|
||||||
|
pub list_item: String,
|
||||||
|
pub create: String,
|
||||||
|
pub update: String,
|
||||||
|
pub publish: String,
|
||||||
|
pub delete: String,
|
||||||
|
pub message_format: String,
|
||||||
|
pub message_not_found: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for QuestStrings {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
list: "Listing {value} quests:".to_string(),
|
||||||
|
list_item: "{n}{q.id}: {q.name}{n} Description: {q.description}".to_string(),
|
||||||
|
create: "Created quest {q.id}".to_string(),
|
||||||
|
update: "Updated quest {q.id}".to_string(),
|
||||||
|
publish: "Published quest {q.id}: {text}".to_string(),
|
||||||
|
delete: "Deleted quest {q.id}".to_string(),
|
||||||
|
message_format: "### `{q.id}` {q.name} (+{q.reward}){n}\
|
||||||
|
Difficulty: *{q.difficulty}*{n}\
|
||||||
|
{q.description}".to_string(),
|
||||||
|
message_not_found: "Warning: quest {q.id} message not found".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue