From d4cffaea0a2f0158832c7f09969a6bd3ed22b582 Mon Sep 17 00:00:00 2001 From: Gabriel Simmer Date: Sat, 21 Oct 2023 19:25:06 +0100 Subject: [PATCH] Switch to fetching instances from group Rather than having configuration file based instance list. Having both is an option but the trend is generally using group instances for events. --- README.md | 13 ++------- src/main.rs | 83 +++++++++++++++++++++++++++++++---------------------- 2 files changed, 50 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index faf46cf..f06c59f 100644 --- a/README.md +++ b/README.md @@ -13,20 +13,11 @@ Not a lot of metrics are collected at the moment. Just the ones I care about. Configuration is done with a `config.toml` file with the following format: ```toml -instances = [ - { name = "optional instance name" - , url = "https://vrchat.com/home/launch?worldId=wrld_123&instanceId=123" - }, - { name = "another instance" - , instance = "234" - , world = "wrld_234" - } -] - +group = "group_7f23c663-e3c5-408f-bfb3-02b164f9e921" vrcdn = "vrcdn_stream_name" ``` -Instances can either be the URL or instance and world ID seperated out, the later taking priority over the former. +Instances will be retrieved from the group. Runs on port `6534`, on all interfaces. May be configurable in the future. diff --git a/src/main.rs b/src/main.rs index ef27951..5eee35e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,3 @@ -use std::collections::HashMap; use std::error::Error; use std::{env, fmt, fs}; @@ -10,11 +9,10 @@ use axum::{ routing::get, Router, }; +use lazy_static::lazy_static; use maud::html; use maud::Markup; use prometheus::{register_gauge_vec, Encoder, GaugeVec, TextEncoder}; - -use lazy_static::lazy_static; use reqwest::header::USER_AGENT; use serde::Deserialize; use url::Url; @@ -68,21 +66,21 @@ impl fmt::Display for WsError { #[derive(Clone, Debug, Deserialize)] struct Config { vrcdn: Option, - instances: Option>, + group: Option, vrchat_token: Option, } #[derive(Clone, Debug, Deserialize)] struct VrcInstance { - /// Full invite URL. Takes precedence over instance and world. - url: Option, /// Instance ID. instance: Option, /// World ID. world: Option, + /// Raw location + location: Option, /// Custom name for the instance. #[serde(skip)] - name: Option + name: Option, } #[derive(Clone, Debug, Deserialize)] @@ -101,6 +99,11 @@ struct VrCdnRegion { total: f64, } +#[derive(Clone, Debug, Deserialize)] +struct VrcGroupInstance { + location: String, +} + #[tokio::main] async fn main() -> Result<(), ()> { let content = fs::read_to_string("config.toml").unwrap(); @@ -111,7 +114,7 @@ async fn main() -> Result<(), ()> { .route("/", get(homepage)) .route("/metrics", get(metrics_handler)) .with_state(config); - + let _ = axum::Server::bind(&"0.0.0.0:6534".parse().unwrap()) .serve(app.into_make_service()) .await; @@ -143,45 +146,52 @@ async fn metrics(config: Config) -> Result, WsError> { let encoder = TextEncoder::new(); let client = reqwest::Client::new(); let auth_cookie = format!("auth={}", &config.vrchat_token.unwrap()); + let mut instances: Vec = vec![]; - // Do work. - for instance in config.instances.ok_or(WsError::Custom("".to_owned()))? { - let (instance_id, world_id): (String, String) = if instance.url.is_none() { - ( - instance.instance.ok_or(WsError::Custom("".to_owned()))?, - instance.world.ok_or(WsError::Custom("".to_owned()))?, - ) - } else { - let url = Url::parse(&instance.url.unwrap_or("".to_owned()))?; - let hash_query: HashMap<_, _> = url.query_pairs().into_owned().collect(); - ( - hash_query - .get("instanceId") - .ok_or(WsError::Custom("".to_owned()))? - .to_string(), - hash_query - .get("worldId") - .ok_or(WsError::Custom("".to_owned()))? - .to_string(), - ) - }; + // Check if we can fetch instances from a group if set. + if config.group.is_some() { let api_url = format!( - "https://api.vrchat.cloud/api/1/instances/{0}:{1}", - world_id, instance_id + "https://api.vrchat.cloud/api/1/groups/{}/instances", + config.group.unwrap() + ); + let url = Url::parse(&api_url).unwrap(); + let req = client + .get(url) + .header( + USER_AGENT, + "vr-event-tracker(git.gmem.ca/arch/vr-event-tracker)", + ) + .header("Cookie", &auth_cookie) + .send() + .await?; + let data: Vec = req.json().await?; + instances = data.into_iter().map(|f| { + let spl: Vec<&str> = f.location.split(":").collect(); + VrcInstance{ instance: Some(spl[0].to_owned()), world: Some(spl[1].to_owned()), location: Some(f.location), name: None } + }).collect(); + } + + for instance in instances { + let api_url = format!( + "https://api.vrchat.cloud/api/1/instances/{}", + &instance.location.clone().unwrap() ); let url = Url::parse(&api_url).unwrap(); let req = client .get(url) - .header(USER_AGENT, "vr-event-tracker(git.gmem.ca/arch/vr-event-tracker)") + .header( + USER_AGENT, + "vr-event-tracker(git.gmem.ca/arch/vr-event-tracker)", + ) .header("Cookie", &auth_cookie) .send() .await?; let data: VrcInstanceData = req.json().await?; - let name = instance.name.unwrap_or(instance_id.clone()); + let name = instance.name.unwrap_or(instance.location.unwrap()); PLAYER_COUNT - .with_label_values(&[&world_id, &instance_id, &name]) + .with_label_values(&[&instance.world.unwrap(), &instance.instance.unwrap(), &name]) .set(data.user_count.unwrap_or(0 as f64)); } @@ -191,7 +201,10 @@ async fn metrics(config: Config) -> Result, WsError> { ); let req = client .get(vrcdn_url) - .header(USER_AGENT, "vr-event-tracker(git.gmem.ca/arch/vr-event-tracker)") + .header( + USER_AGENT, + "vr-event-tracker(git.gmem.ca/arch/vr-event-tracker)", + ) .send() .await .unwrap();