feat: Implementation config
- Bump version to 0.7.0 - Added Config::init_path - Added Error::IsNotImplemented - discord: added implementation config init/load - discord: added /init - discord: added /quest update
This commit is contained in:
parent
b92eaa1241
commit
520992187d
10 changed files with 147 additions and 12 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,4 +1,5 @@
|
||||||
/target
|
/target
|
||||||
/cli/target
|
/cli/target
|
||||||
/discord/target
|
/discord/target
|
||||||
|
/discord/cfg
|
||||||
.env
|
.env
|
||||||
|
|
|
||||||
6
Cargo.lock
generated
6
Cargo.lock
generated
|
|
@ -1599,7 +1599,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "squad-quest"
|
name = "squad-quest"
|
||||||
version = "0.6.0"
|
version = "0.7.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"toml",
|
"toml",
|
||||||
|
|
@ -1607,7 +1607,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "squad-quest-cli"
|
name = "squad-quest-cli"
|
||||||
version = "0.6.0"
|
version = "0.7.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
|
|
@ -1618,7 +1618,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "squad-quest-discord"
|
name = "squad-quest-discord"
|
||||||
version = "0.6.0"
|
version = "0.7.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"dotenvy",
|
"dotenvy",
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
members = ["cli", "discord"]
|
members = ["cli", "discord"]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.6.0"
|
version = "0.7.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
repository = "https://2ndbeam.ru/git/2ndbeam/squad-quest"
|
repository = "https://2ndbeam.ru/git/2ndbeam/squad-quest"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|
|
||||||
|
|
@ -9,5 +9,5 @@ license.workspace = true
|
||||||
chrono = "0.4.42"
|
chrono = "0.4.42"
|
||||||
clap = { version = "4.5.53", features = ["derive"] }
|
clap = { version = "4.5.53", features = ["derive"] }
|
||||||
serde = { version = "1.0.228", features = ["derive"] }
|
serde = { version = "1.0.228", features = ["derive"] }
|
||||||
squad-quest = { version = "0.6.0", path = ".." }
|
squad-quest = { version = "0.7.0", path = ".." }
|
||||||
toml = "0.9.8"
|
toml = "0.9.8"
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,6 @@ clap = { version = "4.5.53", features = ["derive"] }
|
||||||
dotenvy = "0.15.7"
|
dotenvy = "0.15.7"
|
||||||
poise = "0.6.1"
|
poise = "0.6.1"
|
||||||
serde = "1.0.228"
|
serde = "1.0.228"
|
||||||
squad-quest = { version = "0.6.0", path = ".." }
|
squad-quest = { version = "0.7.0", path = ".." }
|
||||||
tokio = { version = "1.48.0", features = ["rt-multi-thread"] }
|
tokio = { version = "1.48.0", features = ["rt-multi-thread"] }
|
||||||
toml = "0.9.8"
|
toml = "0.9.8"
|
||||||
|
|
|
||||||
38
discord/src/commands/init.rs
Normal file
38
discord/src/commands/init.rs
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use poise::serenity_prelude::{ChannelId};
|
||||||
|
use squad_quest::SquadObject;
|
||||||
|
|
||||||
|
use crate::{commands::ERROR_MSG, Context, Error};
|
||||||
|
|
||||||
|
|
||||||
|
#[poise::command(
|
||||||
|
prefix_command,
|
||||||
|
slash_command,
|
||||||
|
required_permissions = "ADMINISTRATOR",
|
||||||
|
guild_only,
|
||||||
|
)]
|
||||||
|
pub async fn init(
|
||||||
|
ctx: Context<'_>,
|
||||||
|
#[description = "Channel to post quests to"]
|
||||||
|
quests_channel: ChannelId,
|
||||||
|
#[description = "Channel to post answers to check"]
|
||||||
|
answers_channel: ChannelId,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let mut dc = ctx.data().discord.clone();
|
||||||
|
let guild = ctx.guild_id().unwrap();
|
||||||
|
dc.quests_channel = quests_channel;
|
||||||
|
dc.answers_channel = answers_channel;
|
||||||
|
dc.guild = guild;
|
||||||
|
let path = &ctx.data().config.full_impl_path().unwrap();
|
||||||
|
let reply_string = match dc.save(path.parent().unwrap_or(Path::new("")).to_owned()) {
|
||||||
|
Ok(_) => "Please restart bot to apply changes".to_string(),
|
||||||
|
Err(error) => {
|
||||||
|
eprintln!("{error}");
|
||||||
|
ERROR_MSG.to_string()
|
||||||
|
},
|
||||||
|
};
|
||||||
|
ctx.reply(reply_string).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
@ -3,7 +3,9 @@
|
||||||
use crate::{Context, Error};
|
use crate::{Context, Error};
|
||||||
|
|
||||||
pub mod quest;
|
pub mod quest;
|
||||||
|
pub mod init;
|
||||||
|
|
||||||
|
pub const ERROR_MSG: &str = "Server error :(";
|
||||||
|
|
||||||
#[poise::command(prefix_command)]
|
#[poise::command(prefix_command)]
|
||||||
pub async fn register(ctx: Context<'_>) -> Result<(), Error> {
|
pub async fn register(ctx: Context<'_>) -> Result<(), Error> {
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ const ERROR_MSG: &str = "Server error :(";
|
||||||
#[poise::command(
|
#[poise::command(
|
||||||
prefix_command,
|
prefix_command,
|
||||||
slash_command,
|
slash_command,
|
||||||
subcommands("list", "create"),
|
subcommands("list", "create", "update"),
|
||||||
)]
|
)]
|
||||||
pub async fn quest(
|
pub async fn quest(
|
||||||
_ctx: Context<'_>,
|
_ctx: Context<'_>,
|
||||||
|
|
@ -146,3 +146,82 @@ pub async fn create(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[poise::command(
|
||||||
|
prefix_command,
|
||||||
|
slash_command,
|
||||||
|
required_permissions = "ADMINISTRATOR",
|
||||||
|
guild_only,
|
||||||
|
)]
|
||||||
|
pub async fn update(
|
||||||
|
ctx: Context<'_>,
|
||||||
|
#[description = "Quest identifier"]
|
||||||
|
id: u16,
|
||||||
|
#[description = "Quest difficulty"]
|
||||||
|
difficulty: Option<DifficultyWrapper>,
|
||||||
|
#[description = "Reward for the quest"]
|
||||||
|
reward: Option<u32>,
|
||||||
|
#[description = "Quest name"]
|
||||||
|
name: Option<String>,
|
||||||
|
#[description = "Quest description"]
|
||||||
|
description: Option<String>,
|
||||||
|
#[description = "Quest answer, visible to admins"]
|
||||||
|
answer: Option<String>,
|
||||||
|
#[description = "Date of publication (in format of YYYY-MM-DD, e.g. 2025-12-24)"]
|
||||||
|
available: Option<DateWrapper>,
|
||||||
|
#[description = "Quest deadline (in format of YYYY-MM-DD, e.g. 2025-12-24)"]
|
||||||
|
deadline: Option<DateWrapper>,
|
||||||
|
#[description = "Clear availability and deadline if checked"]
|
||||||
|
#[rename = "override"]
|
||||||
|
should_override: Option<bool>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let conf = &ctx.data().config;
|
||||||
|
let quests = conf.load_quests();
|
||||||
|
let Some(quest) = quests.iter().find(|q| q.id == id) else {
|
||||||
|
let reply_string = format!("Quest #{id} not found");
|
||||||
|
ctx.reply(reply_string).await?;
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
let difficulty = match difficulty {
|
||||||
|
Some(d) => d.into(),
|
||||||
|
None => quest.difficulty
|
||||||
|
};
|
||||||
|
|
||||||
|
let available_on: Option<Date>;
|
||||||
|
let dead_line: Option<Date>;
|
||||||
|
|
||||||
|
match should_override.unwrap_or(false) {
|
||||||
|
true => {
|
||||||
|
available_on = available.map(|v| v.into());
|
||||||
|
dead_line = deadline.map(|v| v.into());
|
||||||
|
},
|
||||||
|
false => {
|
||||||
|
available_on = available.map_or_else(|| quest.available_on.clone(), |v| Some(v.into()));
|
||||||
|
dead_line = deadline.map_or_else(|| quest.deadline.clone(), |v| Some(v.into()));
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
let new_quest = Quest {
|
||||||
|
id,
|
||||||
|
difficulty,
|
||||||
|
reward: reward.unwrap_or(quest.reward),
|
||||||
|
name: name.unwrap_or(quest.name.clone()),
|
||||||
|
description: description.unwrap_or(quest.description.clone()),
|
||||||
|
answer: answer.unwrap_or(quest.answer.clone()),
|
||||||
|
public: quest.public,
|
||||||
|
available_on,
|
||||||
|
deadline: dead_line,
|
||||||
|
};
|
||||||
|
|
||||||
|
let path = conf.full_quests_path();
|
||||||
|
let reply_string = match new_quest.save(path) {
|
||||||
|
Err(error) => {
|
||||||
|
eprintln!("{error}");
|
||||||
|
ERROR_MSG.to_string()
|
||||||
|
},
|
||||||
|
Ok(_) => format!("Updated quest #{id}"),
|
||||||
|
};
|
||||||
|
ctx.reply(reply_string).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use std::{io::Write, path::PathBuf};
|
use std::{io::Write, path::{Path, PathBuf}};
|
||||||
|
|
||||||
use poise::serenity_prelude::{ChannelId, GuildId, MessageId};
|
use poise::serenity_prelude::{ChannelId, GuildId, MessageId};
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
|
|
@ -6,18 +6,26 @@ use squad_quest::{SquadObject, config::Config, error::Error};
|
||||||
|
|
||||||
pub trait ConfigImpl {
|
pub trait ConfigImpl {
|
||||||
fn discord_impl(&self) -> Result<DiscordConfig, Error>;
|
fn discord_impl(&self) -> Result<DiscordConfig, 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, Error> {
|
||||||
let Some(path) = &self.impl_path else {
|
let Some(path) = &self.full_impl_path() else {
|
||||||
return Err(Error::IsNotImplemented);
|
return Err(Error::IsNotImplemented);
|
||||||
};
|
};
|
||||||
DiscordConfig::load(path.clone())
|
DiscordConfig::load(path.clone())
|
||||||
}
|
}
|
||||||
|
fn init_impl(&self) -> Result<(), Error> {
|
||||||
|
let Some(path) = self.full_impl_path() else {
|
||||||
|
return Err(Error::IsNotImplemented);
|
||||||
|
};
|
||||||
|
let dc = DiscordConfig::default();
|
||||||
|
dc.save(path.parent().unwrap_or(Path::new("")).to_owned())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize, Default, Clone)]
|
||||||
pub struct DiscordConfig {
|
pub struct DiscordConfig {
|
||||||
pub guild: GuildId,
|
pub guild: GuildId,
|
||||||
pub quests_channel: ChannelId,
|
pub quests_channel: ChannelId,
|
||||||
|
|
|
||||||
|
|
@ -22,14 +22,21 @@ async fn main() {
|
||||||
|
|
||||||
let cli = cli::Cli::parse();
|
let cli = cli::Cli::parse();
|
||||||
let config = Config::load(cli.config.clone());
|
let config = Config::load(cli.config.clone());
|
||||||
let discord = config.discord_impl().expect("config does not define impl_path");
|
let discord = config.discord_impl().unwrap_or_else(|_| {
|
||||||
|
config.init_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()
|
||||||
.options(poise::FrameworkOptions {
|
.options(poise::FrameworkOptions {
|
||||||
commands: vec![commands::quest::quest(), commands::register()],
|
commands: vec![
|
||||||
|
commands::quest::quest(),
|
||||||
|
commands::register(),
|
||||||
|
commands::init::init()
|
||||||
|
],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.setup(|ctx, _ready, framework| {
|
.setup(|ctx, _ready, framework| {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue