Initial Commit

This commit is contained in:
Daan Boerlage 2023-04-05 02:32:25 +02:00
commit 97c1eca378
Signed by: daan
GPG key ID: FCE070E1E4956606
8 changed files with 1278 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

13
.woodpacker.yml Normal file
View 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

File diff suppressed because it is too large Load diff

11
Cargo.toml Normal file
View 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
View file

@ -0,0 +1,4 @@
FROM scratch
COPY ${CI_WORKSPACE}/target/release/osrs-playercount /bootstrap
CMD ["/bootstrap"]

13
readme.md Normal file
View 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
View 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
View 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();
}