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.
This commit is contained in:
parent
3ac0d13780
commit
d4cffaea0a
13
README.md
13
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:
|
Configuration is done with a `config.toml` file with the following format:
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
instances = [
|
group = "group_7f23c663-e3c5-408f-bfb3-02b164f9e921"
|
||||||
{ name = "optional instance name"
|
|
||||||
, url = "https://vrchat.com/home/launch?worldId=wrld_123&instanceId=123"
|
|
||||||
},
|
|
||||||
{ name = "another instance"
|
|
||||||
, instance = "234"
|
|
||||||
, world = "wrld_234"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
vrcdn = "vrcdn_stream_name"
|
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.
|
Runs on port `6534`, on all interfaces. May be configurable in the future.
|
||||||
|
|
||||||
|
|
81
src/main.rs
81
src/main.rs
|
@ -1,4 +1,3 @@
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::{env, fmt, fs};
|
use std::{env, fmt, fs};
|
||||||
|
|
||||||
|
@ -10,11 +9,10 @@ use axum::{
|
||||||
routing::get,
|
routing::get,
|
||||||
Router,
|
Router,
|
||||||
};
|
};
|
||||||
|
use lazy_static::lazy_static;
|
||||||
use maud::html;
|
use maud::html;
|
||||||
use maud::Markup;
|
use maud::Markup;
|
||||||
use prometheus::{register_gauge_vec, Encoder, GaugeVec, TextEncoder};
|
use prometheus::{register_gauge_vec, Encoder, GaugeVec, TextEncoder};
|
||||||
|
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use reqwest::header::USER_AGENT;
|
use reqwest::header::USER_AGENT;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
@ -68,21 +66,21 @@ impl fmt::Display for WsError {
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
struct Config {
|
struct Config {
|
||||||
vrcdn: Option<String>,
|
vrcdn: Option<String>,
|
||||||
instances: Option<Vec<VrcInstance>>,
|
group: Option<String>,
|
||||||
vrchat_token: Option<String>,
|
vrchat_token: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
struct VrcInstance {
|
struct VrcInstance {
|
||||||
/// Full invite URL. Takes precedence over instance and world.
|
|
||||||
url: Option<String>,
|
|
||||||
/// Instance ID.
|
/// Instance ID.
|
||||||
instance: Option<String>,
|
instance: Option<String>,
|
||||||
/// World ID.
|
/// World ID.
|
||||||
world: Option<String>,
|
world: Option<String>,
|
||||||
|
/// Raw location
|
||||||
|
location: Option<String>,
|
||||||
/// Custom name for the instance.
|
/// Custom name for the instance.
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
name: Option<String>
|
name: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
@ -101,6 +99,11 @@ struct VrCdnRegion {
|
||||||
total: f64,
|
total: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
struct VrcGroupInstance {
|
||||||
|
location: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<(), ()> {
|
async fn main() -> Result<(), ()> {
|
||||||
let content = fs::read_to_string("config.toml").unwrap();
|
let content = fs::read_to_string("config.toml").unwrap();
|
||||||
|
@ -143,45 +146,52 @@ async fn metrics(config: Config) -> Result<Vec<u8>, WsError> {
|
||||||
let encoder = TextEncoder::new();
|
let encoder = TextEncoder::new();
|
||||||
let client = reqwest::Client::new();
|
let client = reqwest::Client::new();
|
||||||
let auth_cookie = format!("auth={}", &config.vrchat_token.unwrap());
|
let auth_cookie = format!("auth={}", &config.vrchat_token.unwrap());
|
||||||
|
let mut instances: Vec<VrcInstance> = vec![];
|
||||||
|
|
||||||
// Do work.
|
// Check if we can fetch instances from a group if set.
|
||||||
for instance in config.instances.ok_or(WsError::Custom("".to_owned()))? {
|
if config.group.is_some() {
|
||||||
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(),
|
|
||||||
)
|
|
||||||
};
|
|
||||||
let api_url = format!(
|
let api_url = format!(
|
||||||
"https://api.vrchat.cloud/api/1/instances/{0}:{1}",
|
"https://api.vrchat.cloud/api/1/groups/{}/instances",
|
||||||
world_id, instance_id
|
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<VrcGroupInstance> = 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 url = Url::parse(&api_url).unwrap();
|
||||||
|
|
||||||
let req = client
|
let req = client
|
||||||
.get(url)
|
.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)
|
.header("Cookie", &auth_cookie)
|
||||||
.send()
|
.send()
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let data: VrcInstanceData = req.json().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
|
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));
|
.set(data.user_count.unwrap_or(0 as f64));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -191,7 +201,10 @@ async fn metrics(config: Config) -> Result<Vec<u8>, WsError> {
|
||||||
);
|
);
|
||||||
let req = client
|
let req = client
|
||||||
.get(vrcdn_url)
|
.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()
|
.send()
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
Loading…
Reference in a new issue