261 lines
8.1 KiB
Rust
261 lines
8.1 KiB
Rust
//! Payphone's main entry point.
|
|
//!
|
|
//! This module is responsible for parsing command line arguments, nothing else.
|
|
//!
|
|
//! # Command Line Arguments
|
|
//!
|
|
//! The command line arguments are parsed using the [`clap`] crate, and can be found in the
|
|
//! [`Args`] struct. The arguments are:
|
|
//!
|
|
//! - `[TOKEN]`: The Discord bot token to run the bot with. This is required.
|
|
//! - `--logging` or `-l`: The minimum level logs have to be to be outputted into the console, or
|
|
//! off for no logs. Defaults to `info`.
|
|
//! - `--database-addr` or `-d`: The address of the database to connect to, a Scylla node.
|
|
//!
|
|
//! The CLI also supports setting argument values via environment variables. The environment
|
|
//! variables are prefixed with `PAYPHONE_`, so for example, the `--token` argument can be set
|
|
//! using the `PAYPHONE_TOKEN` environment variable. The environment variables are used to
|
|
//! override the command line arguments. `.env` files are also supported by default, so you can
|
|
//! create a `.env` file in the root of the project with the following content:
|
|
//!
|
|
//! ```env
|
|
//! PAYPHONE_TOKEN="your_discord_bot_token"
|
|
//! PAYPHONE_LOGGING="info"
|
|
//! PAYPHONE_DATABASE_ADDR="your_database_addr"
|
|
//! ```
|
|
//!
|
|
//! # Parsing Arguments
|
|
//!
|
|
//! The arguments are parsed using the `Args::parse()` method, which returns an instance of the
|
|
//! [`Args`] struct. This struct contains the parsed values of the command line arguments, and
|
|
//! can be used to initialize the bot and start the event loop.
|
|
//!
|
|
//! ```
|
|
//! // Read the environment variables from the `.env` file, if it exists, and load them as proper
|
|
//! // env variables.
|
|
//! dotenvy::dotenv().ok();
|
|
//!
|
|
//! let args = Args::parse();
|
|
//! ```
|
|
//!
|
|
//! # Running the Bot
|
|
//!
|
|
//! To start the bot, run:
|
|
//!
|
|
//! ```shell
|
|
//! $ cargo run -- start [TOKEN]
|
|
//! ```
|
|
//!
|
|
//! To run multiple shards, pass the `--shard` or `-s` argument to the `start` command with the
|
|
//! shard ID and total amount of shards as `id:total`. For example, for a single-shard bot, you'd
|
|
//! set it to `-s 0:1`. ID is zero-indexed (0 means first shard), total is one-indexed (0 means no
|
|
//! shards).
|
|
//!
|
|
//! # Running the Documentation Server
|
|
//!
|
|
//! Payphone has a built-in documentation server that serves the project's documentation and
|
|
//! refreshes it on changes. To start the documentation server, run the following command:
|
|
//!
|
|
//! ```shell
|
|
//! $ cargo run -- docs --open
|
|
//! ```
|
|
//!
|
|
//! The `--open` flag is optional and will open the documentation in the default browser after
|
|
//! starting the server. You can also use the `--bind` argument to specify the address to bind the
|
|
//! server to, which defaults to `0.0.0.0:3000`.
|
|
|
|
use std::{fmt, net::SocketAddr, str::FromStr};
|
|
|
|
use twilight_gateway::ShardId;
|
|
|
|
/// Payphone's argument parser.
|
|
#[derive(clap::Parser)]
|
|
#[command(name = "Payphone CLI")]
|
|
#[command(version, about = "Payphone's command line interface.")]
|
|
pub struct Args {
|
|
/// The minimum level logs have to be to be outputted into the console, or off for no logs.
|
|
#[arg(
|
|
long,
|
|
short,
|
|
value_enum,
|
|
default_value_t = LevelFilter::Info,
|
|
env = "PAYPHONE_LOGGING",
|
|
value_name = "LEVEL",
|
|
long_help = "The minimum level logs have to be to be outputted into the console, or off for no logs."
|
|
)]
|
|
pub logging: LevelFilter,
|
|
|
|
#[command(subcommand)]
|
|
pub command: Option<Commands>,
|
|
}
|
|
|
|
/// An enum with the different logging levels that can be used in the CLI.
|
|
///
|
|
/// This enum is used to parse the `--logging` or `-l` command line argument and then map the value
|
|
/// to [`log::LevelFilter`].
|
|
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum)]
|
|
pub enum LevelFilter {
|
|
/// No logs at all.
|
|
Off,
|
|
|
|
/// The most verbose level, used for debugging purposes.
|
|
Trace,
|
|
|
|
/// Events that are useful to be displayed during development, but not in production.
|
|
Debug,
|
|
|
|
/// Used for general information about the bot's state and milestones.
|
|
Info,
|
|
|
|
/// Used for warnings, when something unexpected happens but the bot can still continue
|
|
/// running.
|
|
Warn,
|
|
|
|
/// Used for errors, when something goes wrong and the bot cannot continue running.
|
|
Error,
|
|
}
|
|
|
|
#[allow(clippy::from_over_into)]
|
|
impl Into<log::LevelFilter> for LevelFilter {
|
|
fn into(self) -> log::LevelFilter {
|
|
match self {
|
|
Self::Off => log::LevelFilter::Off,
|
|
Self::Trace => log::LevelFilter::Trace,
|
|
Self::Debug => log::LevelFilter::Debug,
|
|
Self::Info => log::LevelFilter::Info,
|
|
Self::Warn => log::LevelFilter::Warn,
|
|
Self::Error => log::LevelFilter::Error,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct Shard {
|
|
pub id: u32,
|
|
pub total: u32,
|
|
}
|
|
|
|
impl Shard {
|
|
fn new(id: u32, total: u32) -> Self {
|
|
Shard { id, total }
|
|
}
|
|
}
|
|
|
|
impl Default for Shard {
|
|
fn default() -> Self {
|
|
Shard::new(0, 1)
|
|
}
|
|
}
|
|
|
|
impl FromStr for Shard {
|
|
type Err = &'static str;
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
match s.split_once(":") {
|
|
Some((id, total)) => {
|
|
let id = id.parse().map_err(|_| "Invalid shard ID")?;
|
|
let total = total.parse().map_err(|_| "Invalid shard total")?;
|
|
|
|
Ok(Shard { id, total })
|
|
}
|
|
None => Err("Invalid shard format, expected `id:total`"),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for Shard {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
write!(f, "{}:{}", self.id, self.total)
|
|
}
|
|
}
|
|
|
|
#[allow(clippy::from_over_into)]
|
|
impl Into<ShardId> for Shard {
|
|
fn into(self) -> ShardId {
|
|
ShardId::new(self.id, self.total)
|
|
}
|
|
}
|
|
|
|
/// The available commands for the Payphone CLI and their available arguments.
|
|
#[derive(clap::Subcommand, Clone)]
|
|
pub enum Commands {
|
|
/// Starts the bot with the provided token.
|
|
Start {
|
|
/// The Discord bot token to run the bot with.
|
|
#[arg(env = "PAYPHONE_TOKEN")]
|
|
token: String,
|
|
|
|
/// The socket address of the database to connect to, a Scylla node.
|
|
#[arg(
|
|
long,
|
|
short,
|
|
env = "PAYPHONE_DATABASE_ADDR",
|
|
value_name = "ADDR",
|
|
default_value_t = String::from("127.0.0.1:9042"))
|
|
]
|
|
database_addr: String,
|
|
|
|
/// The username to use when connecting to the database.
|
|
#[arg(long, env = "PAYPHONE_DATABASE_USERNAME", value_name = "USERNAME")]
|
|
database_username: Option<String>,
|
|
|
|
/// The password to use when connecting to the database.
|
|
#[arg(long, env = "PAYPHONE_DATABASE_PASSWORD", value_name = "PASSWORD")]
|
|
database_password: Option<String>,
|
|
|
|
/// The current shard being run.
|
|
#[arg(
|
|
long,
|
|
short,
|
|
env = "PAYPHONE_SHARD",
|
|
value_name = "ID:TOTAL",
|
|
default_value_t = Shard::default())
|
|
]
|
|
shard: Shard,
|
|
|
|
/// The Redis URLs to connect to.
|
|
#[arg(
|
|
long,
|
|
env = "PAYPHONE_REDIS_URLS",
|
|
value_name = "URLS",
|
|
default_values = &["redis://127.0.0.1:6379"],
|
|
)]
|
|
redis_urls: Vec<String>,
|
|
|
|
/// The bot owner's ID, this user will be allowed to assign moderators.
|
|
#[arg(
|
|
long,
|
|
env = "PAYPHONE_OWNER_ID",
|
|
value_name = "ID",
|
|
default_value_t = 0
|
|
)]
|
|
bot_owner_id: u64,
|
|
},
|
|
|
|
/// Starts a documentation server for the project which serves `cargo doc` and refreshes on
|
|
/// changes.
|
|
Docs {
|
|
/// The address to run the documentation server on.
|
|
#[arg(
|
|
long,
|
|
short,
|
|
env = "PAYPHONE_DOCS_ADDR",
|
|
value_name = "ADDR",
|
|
default_value_t = SocketAddr::from(([0, 0, 0, 0], 3000))
|
|
)]
|
|
bind: SocketAddr,
|
|
|
|
/// When this flag is set, the documentation server will open the documentation in the
|
|
/// default browser after starting the server.
|
|
#[arg(
|
|
long,
|
|
short,
|
|
env = "PAYPHONE_DOCS_OPEN",
|
|
value_name = "OPEN",
|
|
default_value_t = false,
|
|
help = "Open the documentation in the default browser after starting the server."
|
|
)]
|
|
open: bool,
|
|
},
|
|
}
|