-
-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
14 changed files
with
716 additions
and
336 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
[package] | ||
name = "vrc-log" | ||
version = "0.7.1" | ||
version = "0.8.0" | ||
authors = ["Shayne Hartford <[email protected]>"] | ||
edition = "2021" | ||
description = "VRChat Local Cache Avatar ID Logger" | ||
|
@@ -15,6 +15,7 @@ categories = ["config", "database", "filesystem", "games", "parsing"] | |
|
||
[dependencies] | ||
anyhow = "1" | ||
cached = { version = "0.53", optional = true } | ||
chrono = "0.4" | ||
colored = "2" | ||
crossbeam = "0.8" | ||
|
@@ -35,11 +36,18 @@ tracing = "0.1" | |
tracing-subscriber = { version = "0.3", features = ["env-filter", "time"] } | ||
|
||
[features] | ||
default = ["cache", "sqlite", "title", "vrcdb"] | ||
cache = [] | ||
discord = ["dep:discord-presence"] | ||
sqlite = ["dep:sqlite"] | ||
default = ["cache", "title", "doughnut", "neko", "vrcdb"] | ||
# default = ["cache", "title", "avtrdb", "doughnut", "jeff", "neko", "vrcdb"] | ||
|
||
discord = ["dep:discord-presence", "dep:cached"] | ||
title = ["dep:crossterm"] | ||
|
||
# VRChat Avatar Database Providers | ||
avtrdb = ["dep:reqwest", "discord"] | ||
cache = ["dep:sqlite"] | ||
doughnut = ["dep:reqwest", "discord"] | ||
jeff = ["dep:reqwest", "discord"] | ||
neko = ["dep:reqwest", "discord"] | ||
vrcdb = ["dep:reqwest", "discord"] | ||
|
||
# https://github.com/johnthagen/min-sized-rust | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,37 +1,79 @@ | ||
use std::{ | ||
sync::{Arc, LazyLock}, | ||
time::Duration, | ||
}; | ||
use std::{sync::Arc, time::Duration}; | ||
|
||
use cached::proc_macro::once; | ||
use discord_presence::{ | ||
models::{EventData, PartialUser}, | ||
Client, | ||
}; | ||
use parking_lot::RwLock; | ||
use parking_lot::Mutex; | ||
|
||
pub const CLIENT_ID: u64 = 1_137_885_877_918_502_923; | ||
pub const DEVELOPER_ID: &str = "358558305997684739"; | ||
|
||
pub static USER: LazyLock<Option<PartialUser>> = LazyLock::new(|| { | ||
let user_event = Arc::new(RwLock::new(None)); | ||
let user_clone = user_event.clone(); | ||
let mut client = Client::new(CLIENT_ID); | ||
client | ||
.on_ready(move |ctx| { | ||
if let EventData::Ready(event) = ctx.event { | ||
*user_event.write() = event.user; | ||
}; | ||
}) | ||
.persist(); | ||
pub struct Discord { | ||
pub client: Client, | ||
pub user: Arc<Mutex<Option<PartialUser>>>, | ||
} | ||
|
||
impl Discord { | ||
fn start() -> Self { | ||
let mut discord = Self { | ||
client: Client::new(CLIENT_ID), | ||
user: Arc::default(), | ||
}; | ||
|
||
let user = discord.user.clone(); | ||
discord | ||
.client | ||
.on_ready(move |ctx| { | ||
if let EventData::Ready(event) = ctx.event { | ||
*user.lock() = event.user; | ||
}; | ||
}) | ||
.persist(); | ||
|
||
discord.client.start(); | ||
discord | ||
} | ||
} | ||
|
||
#[once(sync_writes = true)] | ||
pub fn get_dev_id() -> String { | ||
warn!("Error: Discord RPC Connection Failed\n"); | ||
warn!("This may be due to one of the following reasons:"); | ||
warn!("1. Discord is not running on your system."); | ||
warn!("2. VRC-LOG was restarted too quickly.\n"); | ||
warn!("The User ID will default to the developer: ShayBox"); | ||
|
||
std::env::var("DISCORD").unwrap_or_else(|_| DEVELOPER_ID.to_owned()) | ||
} | ||
|
||
client.start(); | ||
#[once(option = true, sync_writes = true)] | ||
pub fn get_user_id() -> Option<String> { | ||
let discord = Discord::start(); | ||
let user = discord.user.lock().clone(); | ||
|
||
// block_until_event will never timeout | ||
std::thread::sleep(Duration::from_secs(5)); | ||
discord.client.shutdown().ok()?; | ||
|
||
client.shutdown().expect("Failed to stop RPC thread"); | ||
Some(match user { | ||
None => get_dev_id(), | ||
Some(user) => { | ||
let userid = user.id.unwrap_or_else(get_dev_id); | ||
if userid == "1045800378228281345" { | ||
warn!("Vesktop & arRPC doesn't support fetching user info"); | ||
warn!("You can supply the 'DISCORD' env variable manually"); | ||
warn!("The User ID will default to the developer: ShayBox"); | ||
|
||
let user = user_clone.read(); | ||
std::env::var("DISCORD").unwrap_or_else(|_| DEVELOPER_ID.to_owned()) | ||
} else { | ||
if let Some(username) = user.username { | ||
info!("[Discord] Authenticated as {username}"); | ||
} | ||
|
||
user.clone() | ||
}); | ||
userid | ||
} | ||
} | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,6 +28,11 @@ pub mod vrchat; | |
|
||
pub const CARGO_PKG_VERSION: &str = env!("CARGO_PKG_VERSION"); | ||
pub const CARGO_PKG_HOMEPAGE: &str = env!("CARGO_PKG_HOMEPAGE"); | ||
pub const USER_AGENT: &str = concat!( | ||
"VRC-LOG/", | ||
env!("CARGO_PKG_VERSION"), | ||
" [email protected]" | ||
); | ||
|
||
pub type WatchResponse = (Sender<PathBuf>, Receiver<PathBuf>, PollWatcher); | ||
|
||
|
@@ -37,7 +42,6 @@ pub fn get_local_time() -> String { | |
} | ||
|
||
/// # Errors | ||
/// | ||
/// Will return `Err` if couldn't get the GitHub repository | ||
pub fn check_for_updates() -> reqwest::Result<bool> { | ||
let response = reqwest::blocking::get(CARGO_PKG_HOMEPAGE)?; | ||
|
@@ -53,7 +57,6 @@ pub fn check_for_updates() -> reqwest::Result<bool> { | |
} | ||
|
||
/// # Errors | ||
/// | ||
/// Will return `Err` if `PollWatcher::watch` errors | ||
pub fn watch<P: AsRef<Path>>(path: P) -> notify::Result<WatchResponse> { | ||
let (tx_a, rx_a) = crossbeam::channel::unbounded(); | ||
|
@@ -83,11 +86,8 @@ pub fn watch<P: AsRef<Path>>(path: P) -> notify::Result<WatchResponse> { | |
/// Steam Game Launch Options: `.../vrc-log(.exe) %command%` | ||
/// | ||
/// # Errors | ||
/// | ||
/// Will return `Err` if `Command::spawn` errors | ||
/// | ||
/// # Panics | ||
/// | ||
/// Will panic if `Child::wait` panics | ||
pub fn launch_game(args: Args) -> anyhow::Result<()> { | ||
let args = args.collect::<Vec<_>>(); | ||
|
@@ -108,21 +108,26 @@ pub fn launch_game(args: Args) -> anyhow::Result<()> { | |
} | ||
|
||
/// # Errors | ||
/// | ||
/// Will return `Err` if `Sqlite::new` or `Provider::send_avatar_id` errors | ||
pub fn process_avatars((_tx, rx, _): WatchResponse) -> anyhow::Result<()> { | ||
#[cfg_attr(not(feature = "cache"), allow(unused_mut))] | ||
let mut providers = Providers::from([ | ||
#[cfg(all(feature = "cache", feature = "sqlite"))] | ||
(Type::Cache, box_db!(Sqlite::new()?)), | ||
#[cfg(feature = "cache")] | ||
(Type::CACHE, box_db!(Cache::new()?)), | ||
#[cfg(feature = "avtrdb")] | ||
(Type::AVTRDB, box_db!(AvtrDB::default())), | ||
#[cfg(feature = "doughnut")] | ||
(Type::DOUGHNUT, box_db!(Doughnut::default())), | ||
#[cfg(feature = "jeff")] | ||
(Type::JEFF, box_db!(Jeff::default())), | ||
#[cfg(feature = "neko")] | ||
(Type::NEKO, box_db!(Neko::default())), | ||
#[cfg(feature = "vrcdb")] | ||
(Type::VRCDB, box_db!(VRCDB::default())), | ||
#[cfg(all(feature = "sqlite", not(feature = "cache")))] | ||
(Type::Sqlite, box_db!(Sqlite::new()?)), | ||
]); | ||
|
||
#[cfg(feature = "cache")] | ||
let cache = providers.shift_remove(&Type::Cache).context("None")?; | ||
let cache = providers.shift_remove(&Type::CACHE).context("None")?; | ||
|
||
while let Ok(path) = rx.recv() { | ||
let avatar_ids = self::parse_avatar_ids(&path); | ||
|
@@ -163,11 +168,9 @@ pub fn process_avatars((_tx, rx, _): WatchResponse) -> anyhow::Result<()> { | |
} | ||
|
||
/// # Errors | ||
/// | ||
/// Will return `Err` if `std::fs::canonicalize` errors | ||
/// | ||
/// # Panics | ||
/// | ||
/// Will panic if an environment variable doesn't exist | ||
pub fn parse_path_env(path: &str) -> Result<PathBuf, Error> { | ||
let path = regex_replace_all!(r"(?:\$|%)(\w+)%?", path, |_, env| { | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
use std::{collections::HashMap, time::Duration}; | ||
|
||
use anyhow::{bail, Result}; | ||
use reqwest::blocking::Client; | ||
|
||
use crate::{ | ||
provider::{Provider, Type}, | ||
USER_AGENT, | ||
}; | ||
|
||
pub struct AvtrDB { | ||
client: Client, | ||
userid: String, | ||
} | ||
|
||
impl Default for AvtrDB { | ||
fn default() -> Self { | ||
Self { | ||
client: Client::default(), | ||
userid: crate::discord::get_user_id().unwrap(), | ||
} | ||
} | ||
} | ||
|
||
impl Provider for AvtrDB { | ||
fn check_avatar_id(&self, _avatar_id: &str) -> Result<bool> { | ||
bail!("Cache Only") | ||
} | ||
|
||
fn send_avatar_id(&self, avatar_id: &str) -> Result<bool> { | ||
let response = self | ||
.client | ||
.put("...") | ||
.header("User-Agent", USER_AGENT) | ||
.json(&HashMap::from([ | ||
("id", avatar_id), | ||
("userid", &self.userid), | ||
])) | ||
.send()?; | ||
|
||
let status = response.status(); | ||
debug!("[{}] {status} | {}", Type::AvtrDB, response.text()?); | ||
|
||
let unique = match status.as_u16() { | ||
200 | 404 => false, | ||
201 => true, | ||
429 => { | ||
warn!("[{}] 429 Rate Limit, Please Wait 1 Minute...", Type::AvtrDB); | ||
std::thread::sleep(Duration::from_secs(60)); | ||
self.send_avatar_id(avatar_id)? | ||
} | ||
_ => { | ||
error!("[{}] {status}", Type::AvtrDB); | ||
false | ||
} | ||
}; | ||
|
||
Ok(unique) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.