init
This commit is contained in:
commit
83ad349f30
16 changed files with 3428 additions and 0 deletions
16
src/alert_types.rs
Normal file
16
src/alert_types.rs
Normal 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
84
src/alerts.rs
Normal 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
51
src/html/index.html
Normal 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
47
src/main.rs
Normal 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
18
src/misc.rs
Normal 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
33
src/types.rs
Normal 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
115
src/zones.rs
Normal 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,
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue