Skip to main content

vrc_log/
settings.rs

1use std::collections::HashMap;
2
3use anyhow::Result;
4use derive_config::DeriveTomlConfig;
5use inquire::{
6    list_option::ListOption,
7    validator::{ErrorMessage, Validation},
8    Confirm,
9    MultiSelect,
10    Select,
11};
12use serde::{Deserialize, Serialize};
13use strum::{Display, IntoEnumIterator};
14
15use crate::{discord, discord::DEVELOPER_ID, provider::ProviderKind};
16
17#[derive(Display, Deserialize, Serialize, Default)]
18pub enum Attribution {
19    #[strum(to_string = "Anonymously (VRC-LOG Dev)")]
20    #[default]
21    Anonymous,
22    #[strum(to_string = "Discord RPC ({0})")]
23    DiscordRPC(String),
24    #[strum(to_string = "Discord ID (Manual Input)")]
25    DiscordID(String),
26}
27
28impl Attribution {
29    #[must_use]
30    pub fn get_user_id(&self) -> String {
31        match self {
32            Self::Anonymous => DEVELOPER_ID.to_string(),
33            Self::DiscordID(id) => id.clone(),
34            Self::DiscordRPC(id) => discord::get_user()
35                .and_then(|u| u.id)
36                .unwrap_or_else(|| id.clone()),
37        }
38    }
39}
40
41#[derive(DeriveTomlConfig, Deserialize, Serialize, Default)]
42pub struct Settings {
43    pub attribution:     Attribution,
44    pub clear_amplitude: bool,
45    pub print_scanned:   bool,
46    pub providers:       HashMap<ProviderKind, bool>,
47}
48
49impl Settings {
50    /// # Setup Wizard
51    ///
52    /// # Errors
53    ///
54    /// Will return `Err` if prompts fail.
55    ///
56    /// # Panics
57    ///
58    /// Will panic if Discord user ID doesn't exist.
59    pub fn try_wizard() -> Result<Self> {
60        let mut attributions = vec![
61            Attribution::Anonymous,
62            Attribution::DiscordID(String::new()),
63        ];
64        if let Some(user) = discord::get_user() {
65            attributions.insert(1, Attribution::DiscordRPC(user.id.unwrap()));
66        }
67        let attribution = Select::new("How do you want to be credited?", attributions).prompt()?;
68        let providers = {
69            let providers = ProviderKind::iter().collect::<Vec<_>>();
70            let enabled = MultiSelect::new("Select which providers to use:", providers.clone())
71                .with_page_size(providers.len())
72                .with_default(&[0, 1, 2, 3, 4, 5, 6, 7])
73                .with_validator(|list: &[ListOption<&ProviderKind>]| {
74                    if list.is_empty() {
75                        let message = String::from("You must select at least one.");
76                        Ok(Validation::Invalid(ErrorMessage::Custom(message)))
77                    } else {
78                        Ok(Validation::Valid)
79                    }
80                })
81                .prompt()?;
82
83            providers
84                .iter()
85                .map(|provider| (*provider, enabled.contains(provider)))
86                .collect()
87        };
88
89        let clear_amplitude = Confirm::new(
90            "Clear amplitude file after reading? (Helps with privacy by removing tracked data)",
91        )
92        .with_default(true)
93        .prompt()?;
94
95        let print_scanned = Confirm::new(
96            "Print all scanned avatar ids instead of just the uniquely discovered ones",
97        )
98        .with_default(false)
99        .prompt()?;
100
101        Ok(Self {
102            attribution,
103            clear_amplitude,
104            print_scanned,
105            providers,
106        })
107    }
108}