use anyhow::Context;
use camino::{
Utf8Path,
Utf8PathBuf,
};
use serde::{
Deserialize,
Serialize,
};
use serenity::{
model::prelude::GuildId,
utils::validate_token,
};
use std::{
borrow::Cow,
collections::HashMap,
};
fn default_prefix() -> String {
"p!".to_string()
}
#[derive(Deserialize, Debug)]
pub struct Config {
pub token: String,
pub application_id: u64,
#[serde(default = "default_prefix")]
pub prefix: String,
pub status: Option<StatusConfig>,
pub data_dir: Utf8PathBuf,
pub test_guild: Option<GuildId>,
pub fml: FmlConfig,
pub deviantart: DeviantArtConfig,
pub sauce_nao: SauceNaoConfig,
#[serde(rename = "open-ai")]
pub open_ai: OpenAiConfig,
#[serde(default)]
pub log: LogConfig,
#[serde(flatten)]
pub extra: HashMap<String, toml::Value>,
}
#[derive(Deserialize, Debug)]
pub struct FmlConfig {
pub key: String,
}
#[derive(Deserialize, Debug)]
pub struct DeviantArtConfig {
pub username: String,
pub password: String,
}
#[derive(Deserialize, Debug)]
pub struct SauceNaoConfig {
pub api_key: String,
#[serde(flatten)]
pub extra: HashMap<String, toml::Value>,
}
#[derive(Deserialize, Debug)]
pub struct OpenAiConfig {
#[serde(rename = "api-key")]
pub api_key: String,
#[serde(flatten)]
pub extra: HashMap<String, toml::Value>,
}
#[derive(Deserialize, Debug)]
pub struct LogConfig {
#[serde(default = "LogConfig::default_directives")]
pub directives: Vec<String>,
#[serde(default, rename = "opentelemetry")]
pub opentelemetry: bool,
pub endpoint: Option<String>,
#[serde(default)]
pub headers: HashMap<String, String>,
}
impl LogConfig {
fn default_directives() -> Vec<String> {
vec![
"pikadick=info".to_string(),
"serenity::framework::standard=info".to_string(),
]
}
}
impl Default for LogConfig {
fn default() -> Self {
Self {
directives: Self::default_directives(),
opentelemetry: false,
endpoint: None,
headers: HashMap::new(),
}
}
}
impl Config {
pub fn status_name(&self) -> Option<&str> {
self.status.as_ref().map(|s| s.name.as_str())
}
pub fn status_url(&self) -> Option<&str> {
self.status.as_ref().and_then(|s| s.url.as_deref())
}
pub fn status_type(&self) -> Option<ActivityKind> {
self.status.as_ref().and_then(|s| s.kind)
}
pub fn log_file_dir(&self) -> Utf8PathBuf {
self.data_dir.join("logs")
}
pub fn cache_dir(&self) -> Utf8PathBuf {
self.data_dir.join("cache")
}
pub fn load_from_path<P>(path: P) -> anyhow::Result<Self>
where
P: AsRef<Utf8Path>,
{
let path = path.as_ref();
std::fs::read_to_string(path)
.with_context(|| format!("failed to read config from '{}'", path))
.and_then(|b| Self::load_from_str(&b))
}
pub fn load_from_str(s: &str) -> anyhow::Result<Self> {
toml::from_str(s).context("failed to parse config")
}
pub fn validate(&mut self) -> Vec<ValidationMessage> {
let mut errors = Vec::with_capacity(3);
if let Err(_e) = validate_token(&self.token) {
errors.push(ValidationMessage {
severity: Severity::Error,
error: ValidationError::InvalidToken,
});
}
if let Some(config) = &self.status {
if let (Some(ActivityKind::Streaming), None) = (config.kind, &config.url) {
errors.push(ValidationMessage {
severity: Severity::Error,
error: ValidationError::MissingStreamUrl,
});
}
if let (None, _) = (config.kind, &config.url) {
errors.push(ValidationMessage {
severity: Severity::Warn,
error: ValidationError::MissingStatusType,
});
}
}
errors
}
}
#[derive(Deserialize, Debug)]
pub struct StatusConfig {
#[serde(rename = "type")]
#[serde(default)]
kind: Option<ActivityKind>,
name: String,
url: Option<String>,
#[serde(flatten)]
pub extra: HashMap<String, toml::Value>,
}
#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq, Deserialize, Serialize, Default)]
pub enum ActivityKind {
Listening,
#[default]
Playing,
Streaming,
}
#[derive(Debug)]
pub struct ValidationMessage {
severity: Severity,
error: ValidationError,
}
impl ValidationMessage {
pub fn severity(&self) -> Severity {
self.severity
}
pub fn error(&self) -> &ValidationError {
&self.error
}
}
#[derive(Debug, thiserror::Error)]
pub enum ValidationError {
#[error("invalid token")]
InvalidToken,
#[error("missing status type")]
MissingStatusType,
#[error("missing stream url type")]
MissingStreamUrl,
#[error("{0}")]
Generic(Cow<'static, str>),
}
#[derive(Copy, Clone, Debug)]
pub enum Severity {
Warn,
Error,
}