This commit is contained in:
41666 2023-06-09 04:42:23 -04:00
commit 83ad349f30
16 changed files with 3428 additions and 0 deletions

16
src/alert_types.rs Normal file
View file

@ -0,0 +1,16 @@
// GENERATED CODE. DO NOT EDIT MANUALLY. Run `cd hack/metagame-gen; cargo run` to generate.
pub fn alert_type(metagame_event_id: i32) -> String {
match metagame_event_id {
167 | 168 | 172 | 173 | 174 | 175 | 194 | 195 | 196 | 197 | 204 | 206 | 207 | 216 | 217
| 218 | 219 | 220 | 221 | 225 | 228 | 229 | 230 | 231 | 232 | 235 => "air".to_string(),
106 | 198 | 199 | 200 | 201 | 233 | 236 | 237 | 238 | 239 | 240 | 241 => {
"sudden_death".to_string()
}
147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 176 | 177 | 178
| 179 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 208 | 209 | 210 | 211 | 212
| 213 | 214 | 215 | 222 | 223 | 224 | 226 | 227 | 248 | 249 | 250 | _ => {
"conquest".to_string()
}
}
}

84
src/alerts.rs Normal file
View file

@ -0,0 +1,84 @@
use crate::{alert_types::alert_type, misc};
use chrono::{DateTime, TimeZone, Utc};
use serde::{Deserialize, Serialize};
use serde_aux::prelude::*;
use std::collections::HashMap;
#[derive(Serialize)]
pub struct Alert {
pub id: i32,
pub zone: i32,
pub end_time: Option<DateTime<Utc>>,
pub start_time: Option<DateTime<Utc>>,
pub alert_type: String,
pub ps2alerts: String,
}
pub async fn get_alerts(world_id: i32) -> Result<Vec<Alert>, ()> {
let response = reqwest::get(format!(
"https://census.daybreakgames.com/s:{}/get/{}/world_event/?world_id={}&type=METAGAME&c:limit=10",
misc::service_id(),
misc::platform(world_id),
world_id
))
.await
.unwrap();
let world_events: WorldEventResponse = match response.json().await {
Ok(world_events) => world_events,
Err(_) => return Err(()),
};
let mut alerts: HashMap<i32, Alert> = HashMap::new();
for world_event in world_events.world_event_list {
let alert = alerts.entry(world_event.id).or_insert(Alert {
id: world_event.metagame_event_id,
zone: world_event.zone_id,
end_time: None,
start_time: None,
alert_type: alert_type(world_event.metagame_event_id),
ps2alerts: format!(
"https://ps2alerts.com/alert/{}-{}",
world_id, world_event.id
),
});
if world_event.metagame_event_state_name == "started" {
alert.start_time = Utc.timestamp_opt(world_event.timestamp as i64, 0).single();
} else if world_event.metagame_event_state_name == "ended" {
alert.end_time = Utc.timestamp_opt(world_event.timestamp as i64, 0).single();
}
}
let mut active_alerts: Vec<Alert> = vec![];
for (_, alert) in alerts {
if alert.end_time.is_none() {
active_alerts.push(alert);
}
}
Ok(active_alerts)
}
#[derive(Deserialize)]
struct WorldEventResponse {
world_event_list: Vec<WorldEvent>,
}
#[derive(Deserialize)]
struct WorldEvent {
#[serde(
rename = "instance_id",
deserialize_with = "deserialize_number_from_string"
)]
id: i32,
#[serde(deserialize_with = "deserialize_number_from_string")]
metagame_event_id: i32,
metagame_event_state_name: String,
#[serde(deserialize_with = "deserialize_number_from_string")]
timestamp: i64,
#[serde(deserialize_with = "deserialize_number_from_string")]
zone_id: i32,
}

51
src/html/index.html Normal file
View file

@ -0,0 +1,51 @@
<!DOCTYPE html>
<meta charset="utf-8" />
<title>Aggregate PlanetSide 2 Population API</title>
<meta name="description" content="Multi-source population API" />
<style>
body {
font-family: sans-serif;
margin: 0;
padding: 0;
background-color: black;
color: white;
}
main {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
flex-direction: column;
font-weight: lighter;
text-align: center;
}
.big-header {
font-size: 2rem;
}
.api-list {
margin-top: 2rem;
}
.api-item {
display: block;
background-color: #334;
padding: 0.5rem 1rem;
border-radius: 0.5rem;
margin: 0.5rem;
text-decoration: none;
color: white;
font-weight: bold;
}
</style>
<main>
<div class="big-header">
low fuss metagame API. get current continents open and alerts.
</div>
<div class="api-list">
<a class="api-item" href="/1">GET /{worldID} ▶️</a>
<a class="api-item" href="/all">GET /all ▶️</a>
</div>
<p>Results are cached for 3 minutes. Requesting faster is a dumb idea.</p>
</main>

47
src/main.rs Normal file
View file

@ -0,0 +1,47 @@
#![feature(slice_group_by)]
use axum::{extract::Path, response::Html, routing::get, Json, Router};
use std::{env, net::SocketAddr};
use types::{FactionPercents, World, Zone};
use zones::get_zone_states;
mod alert_types;
mod alerts;
mod misc;
mod types;
mod zones;
#[tokio::main]
async fn main() {
tracing_subscriber::fmt::init();
let app = Router::new()
.layer(tower_http::trace::TraceLayer::new_for_http())
.route("/:world", get(get_world))
.route("/", get(root));
let addr = SocketAddr::from((
[127, 0, 0, 1],
env::var("PORT")
.unwrap_or("8076".to_string())
.parse()
.unwrap(),
));
tracing::debug!("listening on http://{}", addr);
axum::Server::bind(&addr)
.serve(app.into_make_service())
.await
.unwrap();
}
async fn root() -> Html<&'static str> {
Html(include_str!("./html/index.html"))
}
#[axum::debug_handler]
pub async fn get_world(Path(world): Path<i32>) -> Json<World> {
Json(World {
id: world,
zones: get_zone_states(world).await.unwrap(),
})
}

18
src/misc.rs Normal file
View file

@ -0,0 +1,18 @@
use lazy_static::lazy_static;
use std::env;
lazy_static! {
pub static ref SERVICE_ID: String = env::var("SERVICE_ID").unwrap();
}
pub fn service_id() -> String {
SERVICE_ID.clone()
}
pub fn platform(world_id: i32) -> String {
match world_id {
1000 => "ps2ps4us".to_string(),
2000 => "ps2ps4eu".to_string(),
_ => "ps2".to_string(),
}
}

33
src/types.rs Normal file
View file

@ -0,0 +1,33 @@
use serde::Serialize;
#[derive(Serialize)]
pub struct World {
pub id: i32,
pub zones: Vec<Zone>,
}
#[derive(Serialize)]
pub struct Zone {
pub id: i32,
pub locked: bool,
pub alert: Option<Alert>,
pub faction_control: FactionPercents,
}
#[derive(Serialize)]
pub struct Alert {
pub id: i32,
#[serde(rename = "type")]
pub alert_type: String,
pub start: i64,
pub end: i64,
pub score: FactionPercents,
pub ps2alerts: String,
}
#[derive(Serialize)]
pub struct FactionPercents {
pub vs: f32,
pub nc: f32,
pub tr: f32,
}

115
src/zones.rs Normal file
View file

@ -0,0 +1,115 @@
use crate::{
misc,
types::{FactionPercents, Zone},
};
use lazy_static::lazy_static;
use serde::{Deserialize, Serialize};
use serde_aux::prelude::*;
use std::collections::HashMap;
lazy_static! {
pub static ref ZONE_REGIONS: HashMap<i32, Vec<i32>> = HashMap::from([
(2, vec![2201, 2202, 2203]),
(4, vec![4230, 4240, 4250]),
(6, vec![6001, 6002, 6003]),
(8, vec![18029, 18030, 18062]),
(344, vec![18303, 18304, 18305]),
]);
}
pub async fn get_zone_states(world_id: i32) -> Result<Vec<Zone>, ()> {
let response = reqwest::get(format!(
"https://census.daybreakgames.com/s:{}/get/{}/map/?world_id={}&zone_ids=2,4,6,8,344",
misc::service_id(),
misc::platform(world_id),
world_id,
))
.await
.unwrap();
let map: MapResponse = match response.json().await {
Ok(map) => map,
Err(_) => return Err(()),
};
let mut zones: Vec<Zone> = Vec::new();
for map_zone in map.map_list {
let warpgate_zone_filter = ZONE_REGIONS.get(&map_zone.zone_id).unwrap();
let warpgate_factions = map_zone
.regions
.row
.iter()
.filter(|r| warpgate_zone_filter.contains(&r.row_data.region_id))
.map(|r| r.row_data.faction_id)
.collect::<Vec<i32>>();
let zone = Zone {
id: map_zone.zone_id,
locked: warpgate_factions[0] == warpgate_factions[1]
&& warpgate_factions[1] == warpgate_factions[2],
faction_control: calculate_faction_percents(&map_zone.regions.row),
alert: None,
};
zones.push(zone);
}
Ok(zones)
}
fn calculate_faction_percents(regions: &Vec<MapRegionRowData>) -> FactionPercents {
let groups = regions.group_by(|a, b| a.row_data.faction_id == b.row_data.faction_id);
let mut faction_percents = FactionPercents {
vs: 0.0,
nc: 0.0,
tr: 0.0,
};
for faction in groups {
faction.
}
faction_percents
}
#[derive(Deserialize)]
struct MapResponse {
map_list: Vec<MapZone>,
}
#[derive(Deserialize)]
struct MapZone {
#[serde(rename = "ZoneId", deserialize_with = "deserialize_number_from_string")]
zone_id: i32,
#[serde(rename = "Regions")]
regions: MapRegionRow,
}
#[derive(Deserialize)]
struct MapRegionRow {
#[serde(rename = "Row")]
row: Vec<MapRegionRowData>,
}
#[derive(Deserialize)]
struct MapRegionRowData {
#[serde(rename = "RowData")]
row_data: MapRegion,
}
#[derive(Deserialize)]
struct MapRegion {
#[serde(
rename = "RegionId",
deserialize_with = "deserialize_number_from_string"
)]
region_id: i32,
#[serde(
rename = "FactionId",
deserialize_with = "deserialize_number_from_string"
)]
faction_id: i32,
}