From b933d995c4ddfd1c6f005814de9c3eb8e5e8a295 Mon Sep 17 00:00:00 2001 From: Malte Tammena Date: Thu, 9 Dec 2021 17:21:55 +0100 Subject: [PATCH] Rework env vars and cli flags --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/conf.rs | 151 ++++++++++++++++++++------------ src/discord.rs | 14 +-- src/discord/framework.rs | 2 +- src/discord/framework/filter.rs | 10 +-- src/discord/handler.rs | 8 +- src/discord/util.rs | 8 +- src/influx.rs | 4 +- src/rcon.rs | 4 +- src/state.rs | 4 +- 11 files changed, 125 insertions(+), 84 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 98227bb..f136fbf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -613,7 +613,7 @@ dependencies = [ [[package]] name = "glados" -version = "0.1.2" +version = "0.2.0" dependencies = [ "dotenv", "influx_db_client", diff --git a/Cargo.toml b/Cargo.toml index 605505b..8caedd1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "glados" -version = "0.1.2" +version = "0.2.0" edition = "2021" license = "MIT" diff --git a/src/conf.rs b/src/conf.rs index aa5facb..b745f98 100644 --- a/src/conf.rs +++ b/src/conf.rs @@ -11,16 +11,67 @@ lazy_static! { #[derive(Debug, StructOpt)] pub struct Args { - #[structopt(long, + #[structopt(long, env = "DATA_COLLECTOR_ENABLE", takes_value = false)] + pub enable_data_collector: bool, + + #[structopt(long = "token", short = "t", value_name = "TOKEN", validator = validate_token, - env = "DISCORD_TOKEN", + env = "DISCORD_BOT_TOKEN", hide_env_values = true)] - pub discord_token: String, + pub discord_bot_token: String, + + #[structopt( + long = "guild-id", + short = "g", + value_name = "ID", + env = "DISCORD_GUILD_ID" + )] + pub discord_guild_id: u64, + + #[structopt( + long = "owner", + short = "o", + value_name = "ID", + env = "DISCORD_GUILD_OWNERS", + use_delimiter = true + )] + pub discord_guild_owner: Vec, + + #[structopt( + long = "admin-channel", + value_name = "ID", + env = "DISCORD_GUILD_ADMIN_CHANNEL" + )] + pub discord_admin_channel: u64, + + #[structopt( + long = "prefix", + short = "p", + value_name = "PREFIX", + env = "DISCORD_PREFIX", + default_value = "~" + )] + pub discord_command_prefix: String, + + #[structopt(long, value_name = "URL", env = "INFLUX_HOST")] + pub influx_host: Option, + + #[structopt(long, value_name = "DB", env = "INFLUX_DB")] + pub influx_db: Option, + + #[structopt(long, value_name = "USER", env = "INFLUX_USER")] + pub influx_user: Option, + + #[structopt(long, value_name = "PW", env = "INFLUX_PW", hide_env_values = true)] + pub influx_password: Option, + + #[structopt(long, value_name = "NAME", env = "MC_CAM_GROUP")] + pub mc_cam_group: Option, #[structopt(long, value_name = "URL", env = "RCON_ADDRESS")] - pub rcon_address: String, + pub rcon_address: Option, #[structopt( long, @@ -28,74 +79,58 @@ pub struct Args { env = "RCON_PASSWORD", hide_env_values = true )] - pub rcon_password: String, + pub rcon_password: Option, #[structopt( - long, - short, - value_name = "ID", - env = "DISCORD_OWNERS", - use_delimiter = true - )] - pub owner: Vec, - - #[structopt( - long, - short, - value_name = "PREFIX", - env = "DISCORD_PREFIX", - default_value = "~" - )] - pub prefix: String, - - #[structopt( - long = "db", - short, + long = "state", + short = "s", value_name = "PATH", - env = "DB_PATH", + env = "STATE_PATH", default_value = "state.yml" )] - pub database_path: PathBuf, - - #[structopt(long, short, value_name = "ID", env = "GUILD_ID")] - pub guild_id: u64, - - #[structopt(long, value_name = "ID", env = "GUILD_ADMIN_CHANNEL")] - pub admin_channel: u64, - - #[structopt(long, value_name = "URL", env = "INFLUX_HOST")] - pub influx_host: Url, - - #[structopt(long, value_name = "DB", env = "INFLUX_DB")] - pub influx_db: String, - - #[structopt(long, value_name = "USER", env = "INFLUX_USER")] - pub influx_user: String, - - #[structopt(long, value_name = "PW", env = "INFLUX_PW", hide_env_values = true)] - pub influx_password: String, - - #[structopt(long, value_name = "NAME", env = "MC_CAM_GROUP")] - pub cam_group_name: String, + pub state_path: PathBuf, } fn validate_token(raw: String) -> std::result::Result<(), String> { serenity::client::validate_token(raw).map_err(|_| "Invalid discord token. Maybe a typo?".into()) } +macro_rules! impl_arg_method { + ($arg:ident -> $ty:ty) => { + impl Args { + pub fn $arg(&self) -> $ty { + self.$arg.into() + } + } + }; + ($arg:ident -> $ty:ty: when $enable:ident) => { + impl Args { + pub fn $arg(&self) -> $ty { + assert!( + self.$enable, + stringify!(BUG: $arg used despite bot being disabled) + ); + self.$arg.clone().unwrap().into() + } + } + }; +} + +impl_arg_method!(discord_guild_id -> GuildId); +impl_arg_method!(discord_admin_channel -> ChannelId); +impl_arg_method!(influx_host -> Url: when enable_data_collector); +impl_arg_method!(influx_db -> String: when enable_data_collector); +impl_arg_method!(influx_user -> String: when enable_data_collector); +impl_arg_method!(influx_password -> String: when enable_data_collector); +impl_arg_method!(rcon_address -> String: when enable_data_collector); +impl_arg_method!(rcon_password -> String: when enable_data_collector); +impl_arg_method!(mc_cam_group -> String: when enable_data_collector); + impl Args { pub fn owners(&self) -> HashSet { - if self.owner.is_empty() { + if self.discord_guild_owner.is_empty() { tracing::warn!("You should probably specify at least one `--owner`"); } - self.owner.iter().cloned().collect() - } - - pub fn guild_id(&self) -> GuildId { - self.guild_id.into() - } - - pub fn admin_channel(&self) -> ChannelId { - self.admin_channel.into() + self.discord_guild_owner.iter().cloned().collect() } } diff --git a/src/discord.rs b/src/discord.rs index e1d42b0..f98d3e0 100644 --- a/src/discord.rs +++ b/src/discord.rs @@ -43,15 +43,17 @@ pub struct Bot { impl Bot { pub async fn init() -> Result { - let client = Client::builder(&ARGS.discord_token) + let mut builder = Client::builder(&ARGS.discord_bot_token) .framework(framework::init()) .intents(*INTENTS) .event_handler(Handler::default()) - .type_map_insert::(RconHandle::init().await) - .type_map_insert::(InfluxDb::init()) - .type_map_insert::(State::load().await?) - .await - .map_err(Error::CreatingDiscordClient)?; + .type_map_insert::(State::load().await?); + if ARGS.enable_data_collector { + builder = builder + .type_map_insert::(RconHandle::init().await) + .type_map_insert::(InfluxDb::init()) + } + let client = builder.await.map_err(Error::CreatingDiscordClient)?; Ok(Self { client }) } diff --git a/src/discord/framework.rs b/src/discord/framework.rs index bc136ce..2ccbe66 100644 --- a/src/discord/framework.rs +++ b/src/discord/framework.rs @@ -24,7 +24,7 @@ pub fn init() -> StandardFramework { .configure(|c| { c.with_whitespace(true) .owners(ARGS.owners()) - .prefix(&ARGS.prefix) + .prefix(&ARGS.discord_command_prefix) .delimiters(vec![", ", ","]) }) .help(&BOT_HELP) diff --git a/src/discord/framework/filter.rs b/src/discord/framework/filter.rs index 4747c76..0fe3393 100644 --- a/src/discord/framework/filter.rs +++ b/src/discord/framework/filter.rs @@ -32,7 +32,7 @@ pub async fn filter(ctx: &Context, msg: &Message, args: Args) -> CommandResult { } }; // Fetch guild members - let mut members = ARGS.guild_id().members_iter(ctx).boxed(); + let mut members = ARGS.discord_guild_id().members_iter(ctx).boxed(); // Member that match our filter let mut matched = vec![]; // Filter members @@ -44,14 +44,14 @@ pub async fn filter(ctx: &Context, msg: &Message, args: Args) -> CommandResult { }; // Apply Filter if filter.matches(&member).await { - matched.push(member.user.id); + matched.push(member); }; } // Assemble reply let reply = matched .iter() - .fold(String::from("Filtered members:"), |s, id| { - s + &format!(" {}", id) + .fold(String::from("Filtered members:"), |s, member| { + s + &format!(" {}", member) }); msg.reply(ctx, reply).await?; Ok(()) @@ -110,7 +110,7 @@ impl MemberFilter { let RawMemberFilter { add, sub } = raw_filter; // Map role names to role ids let roles: HashMap<_, _> = ARGS - .guild_id() + .discord_guild_id() .roles(ctx) .await? .into_iter() diff --git a/src/discord/handler.rs b/src/discord/handler.rs index a8a9d6c..dc851ed 100644 --- a/src/discord/handler.rs +++ b/src/discord/handler.rs @@ -29,14 +29,16 @@ impl EventHandler for Handler { .await .log_warn("sanitizing state at startup"); - tokio::spawn(track_server_status(ctx_clone)); + if ARGS.enable_data_collector { + tokio::spawn(track_server_status(ctx_clone)); + } self.setup_done.swap(true, Ordering::Relaxed); } } async fn guild_member_addition(&self, ctx: Context, guild_id: GuildId, new_member: Member) { - if guild_id != ARGS.guild_id { + if guild_id != ARGS.discord_guild_id() { return; } if new_member.user.bot { @@ -52,7 +54,7 @@ impl EventHandler for Handler { user: User, _member_data: Option, ) { - if guild_id != ARGS.guild_id { + if guild_id != ARGS.discord_guild_id() { return; } if user.bot { diff --git a/src/discord/util.rs b/src/discord/util.rs index 18876c9..134b66a 100644 --- a/src/discord/util.rs +++ b/src/discord/util.rs @@ -47,13 +47,15 @@ pub async fn workflow_member_removal(ctx: &Context, user: User) { } async fn admin_channel_say(ctx: &Context, what: impl fmt::Display) -> Result { - ARGS.admin_channel().say(ctx.http.clone(), what).await?; + ARGS.discord_admin_channel() + .say(ctx.http.clone(), what) + .await?; Ok(()) } async fn catch_up_on_guild_members(ctx: &Context) -> Result { let actual_members: HashSet<_> = ARGS - .guild_id() + .discord_guild_id() .members(ctx.http.clone(), None, None) .await? .into_iter() @@ -80,7 +82,7 @@ async fn catch_up_on_guild_members(ctx: &Context) -> Result { for new in actual_members.difference(&known_members) { let new_member = match ARGS - .guild_id() + .discord_guild_id() .member(ctx.http.clone(), new) .await .log_warn("retrieving member based on id") diff --git a/src/influx.rs b/src/influx.rs index ce15861..9deab28 100644 --- a/src/influx.rs +++ b/src/influx.rs @@ -16,8 +16,8 @@ impl TypeMapKey for InfluxKey { impl InfluxDb { pub fn init() -> Self { - let inner = Client::new(ARGS.influx_host.clone(), &ARGS.influx_db) - .set_authentication(&ARGS.influx_user, &ARGS.influx_password); + let inner = Client::new(ARGS.influx_host(), &ARGS.influx_db()) + .set_authentication(&ARGS.influx_user(), &ARGS.influx_password()); Self { inner } } diff --git a/src/rcon.rs b/src/rcon.rs index 04dea0a..8f91eeb 100644 --- a/src/rcon.rs +++ b/src/rcon.rs @@ -83,8 +83,8 @@ impl RconHandle { } async fn connect(&mut self) { - let addr = &ARGS.rcon_address; - let password = &ARGS.rcon_password; + let addr = &ARGS.rcon_address(); + let password = &ARGS.rcon_password(); self.inner = rcon::Connection::builder() .enable_minecraft_quirks(true) .connect(addr, password) diff --git a/src/state.rs b/src/state.rs index b40aaa8..d2e14f9 100644 --- a/src/state.rs +++ b/src/state.rs @@ -37,7 +37,7 @@ pub struct MinecraftPlayer { impl State { pub async fn load() -> Result> { - let db = FileDatabase::load_from_path_or_default(&ARGS.database_path)?; + let db = FileDatabase::load_from_path_or_default(&ARGS.state_path)?; Ok(db) } @@ -94,7 +94,7 @@ impl MinecraftPlayer { let player = MinecraftPlayer { uuid: whois.uuid, ign: whois.nick, - is_cam_acc: parent_group == ARGS.cam_group_name, + is_cam_acc: parent_group == ARGS.mc_cam_group(), }; State::write(ctx, |state| state.known_players.insert(player.clone())) .await