Initial Commit
This commit is contained in:
commit
97c1eca378
8 changed files with 1278 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/target
|
13
.woodpacker.yml
Normal file
13
.woodpacker.yml
Normal file
|
@ -0,0 +1,13 @@
|
|||
pipeline:
|
||||
build:
|
||||
image: rust:1-alpine
|
||||
commands:
|
||||
- cargo build --release
|
||||
containerize:
|
||||
image: docker:23-dind
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
commands:
|
||||
- apk add git
|
||||
- docker buildx build -t git.boerlage.me/daan/osrs-playercount-exporter:latest .
|
||||
- docker push git.boerlage.me/daan/osrs-playercount-exporter:latest
|
1078
Cargo.lock
generated
Normal file
1078
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
11
Cargo.toml
Normal file
11
Cargo.toml
Normal file
|
@ -0,0 +1,11 @@
|
|||
[package]
|
||||
name = "osrs-playercount"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
axum = "0.6.12"
|
||||
reqwest = { version = "0.11", default-features = false, features = ["json", "rustls-tls-webpki-roots"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
4
Dockerfile
Normal file
4
Dockerfile
Normal file
|
@ -0,0 +1,4 @@
|
|||
FROM scratch
|
||||
|
||||
COPY ${CI_WORKSPACE}/target/release/osrs-playercount /bootstrap
|
||||
CMD ["/bootstrap"]
|
13
readme.md
Normal file
13
readme.md
Normal file
|
@ -0,0 +1,13 @@
|
|||
[![status-badge](https://woodpecker.boerlage.me/api/badges/daan/osrs-playercount-exporter/status.svg)](https://woodpecker.boerlage.me/daan/osrs-playercount-exporter)
|
||||
|
||||
# OSRS Player Count Exporter
|
||||
|
||||
Exposes a prometheus `/metrics` endpoint with oldschool runescape word player count data.
|
||||
|
||||
## Runelite Endpoint
|
||||
|
||||
This leaches off of the following runlite endpoint:
|
||||
|
||||
```
|
||||
https://api.runelite.net/runelite-1.9.14-SNAPSHOT/worlds.js
|
||||
```
|
125
src/collector/mod.rs
Normal file
125
src/collector/mod.rs
Normal file
|
@ -0,0 +1,125 @@
|
|||
use serde::{Deserialize, Deserializer};
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::str;
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct Worlds {
|
||||
pub worlds: Vec<World>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum WorldLocation {
|
||||
Germany,
|
||||
USA,
|
||||
UnitedKingdom,
|
||||
Australia,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for WorldLocation {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let field = i16::deserialize(deserializer)?;
|
||||
let world = match field {
|
||||
0 => Self::USA,
|
||||
1 => Self::UnitedKingdom,
|
||||
3 => Self::Australia,
|
||||
7 => Self::Germany,
|
||||
_ => Self::Unknown,
|
||||
};
|
||||
|
||||
Ok(world)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for WorldLocation {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
WorldLocation::Germany => write!(f, "Germany"),
|
||||
WorldLocation::USA => write!(f, "USA"),
|
||||
WorldLocation::UnitedKingdom => write!(f, "UK"),
|
||||
WorldLocation::Australia => write!(f, "Australia"),
|
||||
WorldLocation::Unknown => write!(f, "Unknown"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum WorldType {
|
||||
Members,
|
||||
PVP,
|
||||
Bounty,
|
||||
PVPArena,
|
||||
SkillTotal,
|
||||
QuestSpeedrunning,
|
||||
HighRisk,
|
||||
LastManStanding,
|
||||
NoSaveMode,
|
||||
Tournament,
|
||||
FreshStartWorld,
|
||||
Deadman,
|
||||
Seasonal,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for WorldType {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let field = String::deserialize(deserializer)?;
|
||||
let res = match field.as_str() {
|
||||
"MEMBERS" => Self::Members,
|
||||
"PVP" => Self::PVP,
|
||||
"BOUNTY" => Self::Bounty,
|
||||
"PVP_ARENA" => Self::PVPArena,
|
||||
"SKILL_TOTAL" => Self::SkillTotal,
|
||||
"QUEST_SPEEDRUNNING" => Self::QuestSpeedrunning,
|
||||
"HIGH_RISK" => Self::HighRisk,
|
||||
"LAST_MAN_STANDING" => Self::LastManStanding,
|
||||
"NOSAVE_MODE" => Self::NoSaveMode,
|
||||
"TOURNAMENT" => Self::Tournament,
|
||||
"FRESH_START_WORLD" => Self::FreshStartWorld,
|
||||
"DEADMAN" => Self::Deadman,
|
||||
"SEASONAL" => Self::Seasonal,
|
||||
_ => Self::Unknown,
|
||||
};
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct World {
|
||||
pub id: i16,
|
||||
pub address: String,
|
||||
pub activity: String,
|
||||
pub location: WorldLocation,
|
||||
pub players: i16,
|
||||
pub types: Vec<WorldType>,
|
||||
}
|
||||
|
||||
pub trait PromMetric {
|
||||
fn to_metric_string(self: &Self) -> String;
|
||||
}
|
||||
|
||||
impl PromMetric for World {
|
||||
fn to_metric_string(&self) -> String {
|
||||
//"player_count{id=\"301\",location=\"Germany\,"isMembers=\"true\"} 123"
|
||||
format!(
|
||||
"player_count{{id=\"{}\",location=\"{}\",isMembers=\"{:?}\"}} {}",
|
||||
self.id,
|
||||
self.location,
|
||||
self.is_members(),
|
||||
self.players
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl World {
|
||||
pub fn is_members(&self) -> bool {
|
||||
self.types.contains(&WorldType::Members)
|
||||
}
|
||||
}
|
33
src/main.rs
Normal file
33
src/main.rs
Normal file
|
@ -0,0 +1,33 @@
|
|||
mod collector;
|
||||
|
||||
use axum::routing::get;
|
||||
use axum::Router;
|
||||
use collector::{PromMetric, Worlds};
|
||||
|
||||
async fn metrics() -> String {
|
||||
let resp = reqwest::get("https://api.runelite.net/runelite-1.9.13/worlds.js")
|
||||
.await
|
||||
.unwrap()
|
||||
.json::<Worlds>()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let metric_strings: Vec<String> = resp
|
||||
.worlds
|
||||
.into_iter()
|
||||
.map(|w| w.to_metric_string())
|
||||
.collect();
|
||||
metric_strings.join("\n")
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let app = Router::new().route("/metrics", get(metrics));
|
||||
|
||||
println!("Starting...");
|
||||
|
||||
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
|
||||
.serve(app.into_make_service())
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
Loading…
Reference in a new issue