Skip to content

Commit ad7a089

Browse files
committed
0.13.0 - Setup Wizard
1 parent 7838b81 commit ad7a089

File tree

13 files changed

+670
-270
lines changed

13 files changed

+670
-270
lines changed

Cargo.lock

Lines changed: 349 additions & 54 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "vrc-log"
3-
version = "0.12.2"
3+
version = "0.13.0"
44
authors = ["Shayne Hartford <[email protected]>"]
55
edition = "2021"
66
description = "VRChat Local Avatar ID Logger"
@@ -21,8 +21,10 @@ chrono = "0.4"
2121
colored = "3"
2222
crossbeam = "0.8"
2323
crossterm = { version = "0.29", optional = true }
24+
derive-config = { version = "2", features = ["dirs", "toml"] }
2425
discord-presence = { version = "1", optional = true }
2526
futures = "0.3"
27+
inquire = "0.7"
2628
lazy-regex = "3"
2729
notify = "8"
2830
parking_lot = "0.12"

src/discord.rs

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,7 @@ impl Discord {
3838
}
3939

4040
#[once(option = true, sync_writes = true)]
41-
pub fn get_user_id() -> Option<String> {
42-
if let Ok(user_id) = std::env::var("DISCORD") {
43-
/* TODO: Validate User ID (Snowflake) - Regex? */
44-
return Some(user_id);
45-
}
46-
41+
pub fn get_user() -> Option<PartialUser> {
4742
let discord = Discord::start();
4843
std::thread::sleep(Duration::from_secs(5));
4944
discord.client.shutdown().ok()?;
@@ -55,20 +50,18 @@ pub fn get_user_id() -> Option<String> {
5550
info!("[Discord] Authenticated as {username}");
5651
}
5752

58-
return Some(user_id.clone());
53+
return Some(user.clone());
5954
}
6055

6156
warn!("Vesktop & arRPC doesn't support fetching user info");
6257
warn!("You can supply the 'DISCORD' env variable manually");
63-
warn!("The User ID will default to the developer: ShayBox");
6458
}
6559
} else {
6660
warn!("Error: Discord RPC Connection Failed\n");
6761
warn!("This may be due to one of the following reasons:");
6862
warn!("1. Discord is not running on your system.");
6963
warn!("2. VRC-LOG was restarted too quickly.\n");
70-
warn!("The User ID will default to the developer: ShayBox");
7164
}
7265

73-
Some(String::from(DEVELOPER_ID))
66+
None
7467
}

src/lib.rs

Lines changed: 30 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
extern crate tracing;
33

44
use std::{
5-
collections::VecDeque,
65
env::Args,
76
fs::File,
87
io::{BufRead, BufReader, Error},
@@ -21,11 +20,15 @@ use notify::{Config, Event, PollWatcher, RecursiveMode, Watcher};
2120
use parking_lot::RwLock;
2221
use terminal_link::Link;
2322

24-
use crate::provider::{prelude::*, Provider, Type};
23+
use crate::{
24+
provider::{prelude::*, Provider, ProviderKind},
25+
settings::Settings,
26+
};
2527

2628
#[cfg(feature = "discord")]
2729
pub mod discord;
2830
pub mod provider;
31+
pub mod settings;
2932
pub mod vrchat;
3033

3134
pub const CARGO_PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
@@ -111,37 +114,30 @@ pub fn launch_game(args: Args) -> Result<()> {
111114

112115
/// # Errors
113116
/// Will return `Err` if `Sqlite::new` or `Provider::send_avatar_id` errors
114-
pub async fn process_avatars((_tx, rx): (Sender<PathBuf>, Receiver<PathBuf>)) -> Result<()> {
117+
pub async fn process_avatars(
118+
settings: Settings,
119+
(_tx, rx): (Sender<PathBuf>, Receiver<PathBuf>),
120+
) -> Result<()> {
115121
#[cfg(feature = "cache")]
116122
let cache = Cache::new().await?;
117-
118-
#[cfg(feature = "avtrdb")]
119-
let avtrdb = AvtrDB::default();
120-
121-
#[cfg(feature = "vrcdb")]
122-
let vrcdb = VrcDB::default();
123-
124-
#[cfg(feature = "vrcds")]
125-
let vrcds = VrcDS::default();
126-
127-
#[cfg(feature = "vrcwb")]
128-
let vrcwb = VrcWB::default();
129-
130-
#[cfg(feature = "paw")]
131-
let paw = Paw::default();
132-
133-
let providers = VecDeque::from([
134-
#[cfg(feature = "avtrdb")]
135-
Type::AVTRDB(&avtrdb),
136-
#[cfg(feature = "vrcdb")]
137-
Type::VRCDB(&vrcdb),
138-
#[cfg(feature = "vrcds")]
139-
Type::VRCDS(&vrcds),
140-
#[cfg(feature = "vrcwb")]
141-
Type::VRCWB(&vrcwb),
142-
#[cfg(feature = "paw")]
143-
Type::PAW(&paw),
144-
]);
123+
let providers = settings
124+
.providers
125+
.iter()
126+
.filter_map(|provider| match provider {
127+
#[cfg(feature = "cache")]
128+
ProviderKind::CACHE => None,
129+
#[cfg(feature = "avtrdb")]
130+
ProviderKind::AVTRDB => provider!(AvtrDB::new(&settings)),
131+
#[cfg(feature = "paw")]
132+
ProviderKind::PAW => provider!(Paw::new(&settings)),
133+
#[cfg(feature = "vrcdb")]
134+
ProviderKind::VRCDB => provider!(VrcDB::new(&settings)),
135+
#[cfg(feature = "vrcds")]
136+
ProviderKind::VRCDS => provider!(VrcDS::new(&settings)),
137+
#[cfg(feature = "vrcwb")]
138+
ProviderKind::VRCWB => provider!(VrcWB::new(&settings)),
139+
})
140+
.collect::<Vec<_>>();
145141

146142
while let Ok(path) = rx.recv() {
147143
for avatar_id in parse_avatar_ids(&path) {
@@ -162,18 +158,19 @@ pub async fn process_avatars((_tx, rx): (Sender<PathBuf>, Receiver<PathBuf>)) ->
162158
let results = futures::future::join_all(futures).await;
163159

164160
for (provider, result) in providers.iter().zip(results) {
161+
let kind = provider.kind();
165162
match result {
166163
Ok(unique) => {
167164
if unique {
168-
info!("^ Successfully Submitted to {provider}");
165+
info!("^ Successfully Submitted to {kind}");
169166
}
170167
}
171168
Err(error) => {
172169
#[cfg(feature = "cache")]
173170
{
174171
send_to_cache = false;
175172
}
176-
error!("^ Failed to submit to {provider}: {error}");
173+
error!("^ Failed to submit to {kind}: {error}");
177174
}
178175
}
179176
}

src/main.rs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,23 @@
11
#[macro_use]
22
extern crate tracing;
33

4-
use std::sync::OnceLock;
4+
use std::{io::ErrorKind, sync::OnceLock};
55

66
use anyhow::Result;
77
use chrono::{Local, Offset};
88
#[cfg(feature = "title")]
99
use crossterm::{execute, terminal::SetTitle};
10+
use derive_config::{ConfigError, DeriveTomlConfig};
1011
use notify::PollWatcher;
1112
use terminal_link::Link;
1213
use time::{macros::format_description, UtcOffset};
1314
use tracing::level_filters::LevelFilter;
1415
use tracing_subscriber::{fmt::time::OffsetTime, EnvFilter};
1516
use vrc_log::{
17+
settings::Settings,
1618
vrchat::{VRCHAT_AMP_PATH, VRCHAT_LOW_PATH},
1719
CARGO_PKG_HOMEPAGE,
1820
};
19-
2021
/* Watchers will stop working if they get dropped. */
2122
static WATCHERS: OnceLock<Vec<PollWatcher>> = OnceLock::new();
2223

@@ -46,12 +47,24 @@ async fn main() -> Result<()> {
4647
}
4748

4849
let args = std::env::args();
50+
let settings = Settings::load().unwrap_or_else(|error| match error {
51+
ConfigError::Io(error) if error.kind() == ErrorKind::NotFound => {
52+
info!("Welcome to VRC-LOG! Please follow the setup wizard");
53+
Settings::try_wizard().expect("Failed to setup wizard")
54+
}
55+
error => {
56+
error!("There was an error loading the settings: {error}");
57+
Settings::try_wizard().expect("Failed to setup wizard")
58+
}
59+
});
60+
4961
let (tx, rx) = crossbeam::channel::unbounded();
5062
let _ = WATCHERS.set(vec![
5163
vrc_log::watch(tx.clone(), VRCHAT_AMP_PATH.as_path())?,
5264
vrc_log::watch(tx.clone(), VRCHAT_LOW_PATH.as_path())?,
5365
]);
5466

67+
settings.save()?;
5568
vrc_log::launch_game(args)?;
56-
vrc_log::process_avatars((tx, rx)).await
69+
vrc_log::process_avatars(settings, (tx, rx)).await
5770
}

src/provider/avtrdb.rs

Lines changed: 43 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -4,120 +4,103 @@ use anyhow::{bail, Result};
44
use async_trait::async_trait;
55
use reqwest::{Client, StatusCode, Url};
66
use serde::{Deserialize, Serialize};
7+
use serde_json::{json, Value};
78

89
use crate::{
9-
provider::{Provider, Type},
10+
provider::{Provider, ProviderKind},
11+
settings::Settings,
1012
USER_AGENT,
1113
};
1214

1315
const URL: &str = "https://api.avtrdb.com/v2/";
1416

15-
pub struct AvtrDB {
16-
attribution: Option<String>,
17-
client: Client,
18-
url: Url,
17+
pub struct AvtrDB<'a> {
18+
settings: &'a Settings,
19+
client: Client,
1920
}
2021

21-
impl AvtrDB {
22+
impl<'a> AvtrDB<'a> {
2223
#[must_use]
23-
pub fn new(attribution: Option<String>, url: Url) -> Self {
24+
pub fn new(settings: &'a Settings) -> Self {
2425
Self {
25-
attribution,
26-
url,
27-
..Default::default()
26+
settings,
27+
client: Client::default(),
2828
}
2929
}
3030
}
3131

32-
impl Default for AvtrDB {
33-
fn default() -> Self {
34-
// Only alphanumeric strings up to 30 characters or nothing are allowed.
35-
// if these conditions are not met, the given avatars will not be ingested.
36-
let attribution = std::env::var("AVTRDB_ATTRIBUTION")
37-
.ok()
38-
.or_else(crate::discord::get_user_id);
39-
40-
Self {
41-
attribution,
42-
url: Url::parse(URL).expect("Failed to parse URL"),
43-
client: Client::builder()
44-
.timeout(Duration::from_secs(10))
45-
.user_agent(USER_AGENT)
46-
.build()
47-
.unwrap(),
48-
}
49-
}
50-
}
51-
52-
#[derive(Debug, Serialize)]
53-
struct AvtrDBRequest {
54-
avatar_ids: Vec<String>,
55-
attribution: Option<String>,
56-
}
57-
5832
#[derive(Debug, Deserialize)]
59-
struct AvtrDBResponse {
33+
struct IngestResponse {
6034
valid_avatar_ids: u64,
6135
}
6236

6337
#[derive(Debug, Deserialize)]
64-
struct AvtrDBSearchResponse {
65-
avatars: Vec<serde_json::Value>,
38+
struct SearchResponse {
39+
avatars: Vec<Value>,
6640
}
6741

6842
#[async_trait]
69-
impl Provider for AvtrDB {
43+
impl Provider for AvtrDB<'_> {
44+
fn kind(&self) -> ProviderKind {
45+
ProviderKind::AVTRDB
46+
}
47+
7048
async fn check_avatar_id(&self, avatar_id: &str) -> Result<bool> {
71-
let name = Type::AVTRDB(self);
72-
let mut url = self.url.join("avatar/search")?;
49+
let kind = self.kind();
50+
let mut url = Url::parse(URL)?.join("avatar/search")?;
7351
url.set_query(Some(format!("query={avatar_id}").as_str()));
7452

75-
let response = self.client.get(url).send().await?;
53+
let response = self
54+
.client
55+
.get(url)
56+
.header("User-Agent", USER_AGENT)
57+
.send()
58+
.await?;
59+
7660
let status = response.status();
7761
let text = response.text().await?;
78-
debug!("[{name}] {status} | {text}");
62+
debug!("[{kind}] {status} | {text}");
7963

8064
if status != StatusCode::OK {
81-
bail!("[{name}] Failed to check avatar: {status} | {text}");
65+
bail!("[{kind}] Failed to check avatar: {status} | {text}");
8266
}
8367

84-
let data = serde_json::from_str::<AvtrDBSearchResponse>(&text)?;
68+
let data = serde_json::from_str::<SearchResponse>(&text)?;
8569

8670
Ok(data.avatars.len() == 1)
8771
}
8872

89-
// The API supports batching, but this interface does not
90-
// FIXME: adapt ProviderTrait to support batching
9173
async fn send_avatar_id(&self, avatar_id: &str) -> Result<bool> {
92-
let name = Type::AVTRDB(self);
93-
let request = AvtrDBRequest {
94-
avatar_ids: vec![avatar_id.to_string()],
95-
attribution: self.attribution.clone(),
96-
};
74+
let kind = self.kind();
75+
let json = json!({
76+
"avatar_ids": vec![avatar_id.to_string()],
77+
"attribution": self.settings.attribution.get_user_id(),
78+
});
9779

98-
debug!("[{name}] Sending {:#?}", serde_json::to_string(&request)?);
80+
debug!("[{kind}] Sending {json:#?}");
9981

10082
let response = self
10183
.client
102-
.post(self.url.join("avatar/ingest")?)
103-
.json(&request)
84+
.post(Url::parse(URL)?.join("avatar/ingest")?)
85+
.header("User-Agent", USER_AGENT)
86+
.json(&json)
10487
.timeout(Duration::from_secs(3))
10588
.send()
10689
.await?;
10790

10891
let status = response.status();
10992
let text = response.text().await?;
110-
let data = serde_json::from_str::<AvtrDBResponse>(&text)?;
111-
debug!("[{name}] {status} | {text}");
93+
let data = serde_json::from_str::<IngestResponse>(&text)?;
94+
debug!("[{kind}] {status} | {text}");
11295

11396
let unique = match status {
11497
StatusCode::OK => data.valid_avatar_ids == 1,
11598
StatusCode::TOO_MANY_REQUESTS => {
116-
warn!("[{name}] 429 Rate Limit, trying again in 10 seconds");
99+
warn!("[{kind}] 429 Rate Limit, trying again in 10 seconds");
117100
tokio::time::sleep(Duration::from_secs(10)).await;
118101
Box::pin(self.send_avatar_id(avatar_id)).await?
119102
}
120-
_ => bail!("[{name}] Unknown Error: {status} | {text}"),
103+
_ => bail!("[{kind}] Unknown Error: {status} | {text}"),
121104
};
122105

123106
Ok(unique)

0 commit comments

Comments
 (0)