Rework env vars and cli flags

This commit is contained in:
Malte Tammena 2021-12-09 17:21:55 +01:00
parent 21db37b613
commit b933d995c4
11 changed files with 125 additions and 84 deletions

2
Cargo.lock generated
View file

@ -613,7 +613,7 @@ dependencies = [
[[package]] [[package]]
name = "glados" name = "glados"
version = "0.1.2" version = "0.2.0"
dependencies = [ dependencies = [
"dotenv", "dotenv",
"influx_db_client", "influx_db_client",

View file

@ -1,6 +1,6 @@
[package] [package]
name = "glados" name = "glados"
version = "0.1.2" version = "0.2.0"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"

View file

@ -11,16 +11,67 @@ lazy_static! {
#[derive(Debug, StructOpt)] #[derive(Debug, StructOpt)]
pub struct Args { pub struct Args {
#[structopt(long, #[structopt(long, env = "DATA_COLLECTOR_ENABLE", takes_value = false)]
pub enable_data_collector: bool,
#[structopt(long = "token",
short = "t", short = "t",
value_name = "TOKEN", value_name = "TOKEN",
validator = validate_token, validator = validate_token,
env = "DISCORD_TOKEN", env = "DISCORD_BOT_TOKEN",
hide_env_values = true)] 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<UserId>,
#[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<Url>,
#[structopt(long, value_name = "DB", env = "INFLUX_DB")]
pub influx_db: Option<String>,
#[structopt(long, value_name = "USER", env = "INFLUX_USER")]
pub influx_user: Option<String>,
#[structopt(long, value_name = "PW", env = "INFLUX_PW", hide_env_values = true)]
pub influx_password: Option<String>,
#[structopt(long, value_name = "NAME", env = "MC_CAM_GROUP")]
pub mc_cam_group: Option<String>,
#[structopt(long, value_name = "URL", env = "RCON_ADDRESS")] #[structopt(long, value_name = "URL", env = "RCON_ADDRESS")]
pub rcon_address: String, pub rcon_address: Option<String>,
#[structopt( #[structopt(
long, long,
@ -28,74 +79,58 @@ pub struct Args {
env = "RCON_PASSWORD", env = "RCON_PASSWORD",
hide_env_values = true hide_env_values = true
)] )]
pub rcon_password: String, pub rcon_password: Option<String>,
#[structopt( #[structopt(
long, long = "state",
short, short = "s",
value_name = "ID",
env = "DISCORD_OWNERS",
use_delimiter = true
)]
pub owner: Vec<UserId>,
#[structopt(
long,
short,
value_name = "PREFIX",
env = "DISCORD_PREFIX",
default_value = "~"
)]
pub prefix: String,
#[structopt(
long = "db",
short,
value_name = "PATH", value_name = "PATH",
env = "DB_PATH", env = "STATE_PATH",
default_value = "state.yml" default_value = "state.yml"
)] )]
pub database_path: PathBuf, pub state_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,
} }
fn validate_token(raw: String) -> std::result::Result<(), String> { fn validate_token(raw: String) -> std::result::Result<(), String> {
serenity::client::validate_token(raw).map_err(|_| "Invalid discord token. Maybe a typo?".into()) 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 { impl Args {
pub fn owners(&self) -> HashSet<UserId> { pub fn owners(&self) -> HashSet<UserId> {
if self.owner.is_empty() { if self.discord_guild_owner.is_empty() {
tracing::warn!("You should probably specify at least one `--owner`"); tracing::warn!("You should probably specify at least one `--owner`");
} }
self.owner.iter().cloned().collect() self.discord_guild_owner.iter().cloned().collect()
}
pub fn guild_id(&self) -> GuildId {
self.guild_id.into()
}
pub fn admin_channel(&self) -> ChannelId {
self.admin_channel.into()
} }
} }

View file

@ -43,15 +43,17 @@ pub struct Bot {
impl Bot { impl Bot {
pub async fn init() -> Result<Self> { pub async fn init() -> Result<Self> {
let client = Client::builder(&ARGS.discord_token) let mut builder = Client::builder(&ARGS.discord_bot_token)
.framework(framework::init()) .framework(framework::init())
.intents(*INTENTS) .intents(*INTENTS)
.event_handler(Handler::default()) .event_handler(Handler::default())
.type_map_insert::<data::StateKey>(State::load().await?);
if ARGS.enable_data_collector {
builder = builder
.type_map_insert::<data::Rcon>(RconHandle::init().await) .type_map_insert::<data::Rcon>(RconHandle::init().await)
.type_map_insert::<InfluxKey>(InfluxDb::init()) .type_map_insert::<InfluxKey>(InfluxDb::init())
.type_map_insert::<data::StateKey>(State::load().await?) }
.await let client = builder.await.map_err(Error::CreatingDiscordClient)?;
.map_err(Error::CreatingDiscordClient)?;
Ok(Self { client }) Ok(Self { client })
} }

View file

@ -24,7 +24,7 @@ pub fn init() -> StandardFramework {
.configure(|c| { .configure(|c| {
c.with_whitespace(true) c.with_whitespace(true)
.owners(ARGS.owners()) .owners(ARGS.owners())
.prefix(&ARGS.prefix) .prefix(&ARGS.discord_command_prefix)
.delimiters(vec![", ", ","]) .delimiters(vec![", ", ","])
}) })
.help(&BOT_HELP) .help(&BOT_HELP)

View file

@ -32,7 +32,7 @@ pub async fn filter(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
} }
}; };
// Fetch guild members // 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 // Member that match our filter
let mut matched = vec![]; let mut matched = vec![];
// Filter members // Filter members
@ -44,14 +44,14 @@ pub async fn filter(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
}; };
// Apply Filter // Apply Filter
if filter.matches(&member).await { if filter.matches(&member).await {
matched.push(member.user.id); matched.push(member);
}; };
} }
// Assemble reply // Assemble reply
let reply = matched let reply = matched
.iter() .iter()
.fold(String::from("Filtered members:"), |s, id| { .fold(String::from("Filtered members:"), |s, member| {
s + &format!(" {}", id) s + &format!(" {}", member)
}); });
msg.reply(ctx, reply).await?; msg.reply(ctx, reply).await?;
Ok(()) Ok(())
@ -110,7 +110,7 @@ impl MemberFilter {
let RawMemberFilter { add, sub } = raw_filter; let RawMemberFilter { add, sub } = raw_filter;
// Map role names to role ids // Map role names to role ids
let roles: HashMap<_, _> = ARGS let roles: HashMap<_, _> = ARGS
.guild_id() .discord_guild_id()
.roles(ctx) .roles(ctx)
.await? .await?
.into_iter() .into_iter()

View file

@ -29,14 +29,16 @@ impl EventHandler for Handler {
.await .await
.log_warn("sanitizing state at startup"); .log_warn("sanitizing state at startup");
if ARGS.enable_data_collector {
tokio::spawn(track_server_status(ctx_clone)); tokio::spawn(track_server_status(ctx_clone));
}
self.setup_done.swap(true, Ordering::Relaxed); self.setup_done.swap(true, Ordering::Relaxed);
} }
} }
async fn guild_member_addition(&self, ctx: Context, guild_id: GuildId, new_member: Member) { 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; return;
} }
if new_member.user.bot { if new_member.user.bot {
@ -52,7 +54,7 @@ impl EventHandler for Handler {
user: User, user: User,
_member_data: Option<Member>, _member_data: Option<Member>,
) { ) {
if guild_id != ARGS.guild_id { if guild_id != ARGS.discord_guild_id() {
return; return;
} }
if user.bot { if user.bot {

View file

@ -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 { 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(()) Ok(())
} }
async fn catch_up_on_guild_members(ctx: &Context) -> Result { async fn catch_up_on_guild_members(ctx: &Context) -> Result {
let actual_members: HashSet<_> = ARGS let actual_members: HashSet<_> = ARGS
.guild_id() .discord_guild_id()
.members(ctx.http.clone(), None, None) .members(ctx.http.clone(), None, None)
.await? .await?
.into_iter() .into_iter()
@ -80,7 +82,7 @@ async fn catch_up_on_guild_members(ctx: &Context) -> Result {
for new in actual_members.difference(&known_members) { for new in actual_members.difference(&known_members) {
let new_member = match ARGS let new_member = match ARGS
.guild_id() .discord_guild_id()
.member(ctx.http.clone(), new) .member(ctx.http.clone(), new)
.await .await
.log_warn("retrieving member based on id") .log_warn("retrieving member based on id")

View file

@ -16,8 +16,8 @@ impl TypeMapKey for InfluxKey {
impl InfluxDb { impl InfluxDb {
pub fn init() -> Self { pub fn init() -> Self {
let inner = Client::new(ARGS.influx_host.clone(), &ARGS.influx_db) let inner = Client::new(ARGS.influx_host(), &ARGS.influx_db())
.set_authentication(&ARGS.influx_user, &ARGS.influx_password); .set_authentication(&ARGS.influx_user(), &ARGS.influx_password());
Self { inner } Self { inner }
} }

View file

@ -83,8 +83,8 @@ impl RconHandle {
} }
async fn connect(&mut self) { async fn connect(&mut self) {
let addr = &ARGS.rcon_address; let addr = &ARGS.rcon_address();
let password = &ARGS.rcon_password; let password = &ARGS.rcon_password();
self.inner = rcon::Connection::builder() self.inner = rcon::Connection::builder()
.enable_minecraft_quirks(true) .enable_minecraft_quirks(true)
.connect(addr, password) .connect(addr, password)

View file

@ -37,7 +37,7 @@ pub struct MinecraftPlayer {
impl State { impl State {
pub async fn load() -> Result<FileDatabase<Self, Yaml>> { pub async fn load() -> Result<FileDatabase<Self, Yaml>> {
let db = FileDatabase::load_from_path_or_default(&ARGS.database_path)?; let db = FileDatabase::load_from_path_or_default(&ARGS.state_path)?;
Ok(db) Ok(db)
} }
@ -94,7 +94,7 @@ impl MinecraftPlayer {
let player = MinecraftPlayer { let player = MinecraftPlayer {
uuid: whois.uuid, uuid: whois.uuid,
ign: whois.nick, 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())) State::write(ctx, |state| state.known_players.insert(player.clone()))
.await .await