diff --git a/README.md b/README.md index a0c64ce..d47d489 100644 --- a/README.md +++ b/README.md @@ -2,56 +2,73 @@ PlanetSide 2 live population API. This API is free and open for anyone to use. -The one and only goal of this app is to provide a current "point-in-time" population status for PlanetSide 2, per world, per faction, (and later, per continent.) Historical info is _not_ a goal. - https://saerro.harasse.rs +Our methodology is to add any player ID seen on the Census websockets to a time-sorted set, and returning the number of player IDs seen within 15 minutes. + +--- + +The one and only goal of this app is to provide a current "point-in-time" population status for PlanetSide 2, per world, per faction, (and later, per continent.) Historical info is _not_ a goal; you may implement this on your end. + +Please open an issue here or get in touch with Pomf (okano#0001) on the PS2 Discord if you have complex use cases for this data; it may be trivial/easy to implement APIs tailored to your needs. + +The main use case is for [Medkit](https://github.com/kayteh/medkit2) bot to have an in-house source of population data, without relying too heavily on any third-party stats service, like Fisu, Honu, or Voidwell; which all have different population tracking needs and goals (and thus, different data.) + ## API Reference -- [`/`](https://saerro.harasse.rs) - Shows a help/usage message. -- [`/w/{worldID}`](https://saerro.harasse.rs/w/17) - Shows populations for one world, example: +This API will never return a failure unless the app itself is failing. If you request a world ID that doesn't exist, it only sees an empty set, and will not 404. Since 0 can both mean a server is down and it doesn't exist, be mindful of the data. It could even mean the Websockets have failed. -```json -{ - "worldID": 17, - "total": 1000, - "factions": { - "tr": 334, - "nc": 333, - "vs": 333 +This API only supports GET, and supports CORS. + +- [`/`](https://saerro.harasse.rs) - Shows a help/usage message. + + ```json + { + "worldID": 17, + "total": 1000, + "factions": { + "tr": 334, + "nc": 333, + "vs": 333 + } } -} -``` + ``` - [`/m/?id={worldID1},{worldID2}...`](https://saerro.harasse.rs/m/?id=1,17) - Shows populations for all listed worlds, example: -```json -{ - "worlds": [ - { - "worldID": 17, - "total": 1000, - "factions": { - "tr": 334, - "nc": 333, - "vs": 333 + ```json + { + "worlds": [ + { + "worldID": 17, + "total": 1000, + "factions": { + "tr": 334, + "nc": 333, + "vs": 333 + } + }, + { + "worldID": 19, + "total": 1000, + "factions": { + "tr": 334, + "nc": 333, + "vs": 333 + } } - }, - { - "worldID": 19, - "total": 1000, - "factions": { - "tr": 334, - "nc": 333, - "vs": 333 - } - } - ] -} -``` + ] + } + ``` ## Architecture - Websocket processors - One pair per PC, PS4US, PS4EU - Each pair connects to either Census Websocket or NS Websocket, depending on availability. +- API + - Serves https://saerro.harasse.rs +- Redis + - Using ZADD with score as timestamp, ZCOUNTBYSCORE by timestamp in 15 minute windows, and cleaned up with SCAN+ZREMBYSCORE, population data is tracked. +- Redis "Tender" + - Cleans up Redis every 5 mins. diff --git a/services/api/src/main.rs b/services/api/src/main.rs index 9a4ddb4..a23981d 100644 --- a/services/api/src/main.rs +++ b/services/api/src/main.rs @@ -5,16 +5,35 @@ use serde::{Deserialize, Serialize}; #[macro_use] extern crate serde_json; -#[derive(Deserialize, Serialize)] +#[derive(Serialize, Deserialize)] struct IncomingHeaders { host: String, } +#[derive(Serialize, Deserialize, Debug)] +struct Factions { + tr: u32, + nc: u32, + vs: u32, +} + +#[derive(Serialize, Deserialize, Debug)] +struct WorldPopulation { + world_id: u32, + total: u32, + factions: Factions, +} + +#[derive(Serialize, Deserialize, Debug)] +struct MultipleWorldPopulation { + worlds: Vec, +} + #[handler] async fn info(req: &mut Request, res: &mut Response) { let headers: IncomingHeaders = req.parse_headers().unwrap(); let json = json!({ - "@": "Saerro Listening Post", + "@": "Saerro Listening Post - PlanetSide 2 Live Population API", "@GitHub": "https://github.com/genudine/saerro", "@Disclaimer": "Genudine Dynamics is not responsible for any damages caused by this software. Use at your own risk.", "@Support": "#api-dev in https://discord.com/servers/planetside-2-community-251073753759481856", @@ -34,15 +53,60 @@ async fn info(req: &mut Request, res: &mut Response) { res.render(serde_json::to_string_pretty(&json).unwrap()); } +#[handler] +async fn get_world(req: &mut Request, res: &mut Response) { + let world_id: String = req.param("worldID").unwrap(); + let response = get_world_pop(world_id).await; + + res.render(Json(response)); +} + +#[handler] +async fn get_world_multi(req: &mut Request, res: &mut Response) { + let world_ids_raw = req.query::("ids").unwrap(); + let world_ids: Vec<&str> = world_ids_raw.split(",").collect(); + + let mut response = MultipleWorldPopulation { worlds: Vec::new() }; + + for world_id in world_ids { + response + .worlds + .push(get_world_pop(world_id.to_string()).await); + } + + res.render(Json(response)); +} + +async fn get_world_pop(world_id: String) -> WorldPopulation { + let response = WorldPopulation { + world_id: world_id.parse().unwrap(), + total: 0, + factions: Factions { + tr: 0, + nc: 0, + vs: 0, + }, + }; + + response +} + #[tokio::main] async fn main() { + let port = ::std::env::var("PORT").unwrap_or("7878".to_string()); + let addr = format!("127.0.0.1:{}", port); + let cors_handler = Cors::builder() .allow_any_origin() .allow_method("GET") .build(); - let router = Router::new().hoop(cors_handler).get(info); - Server::new(TcpListener::bind("127.0.0.1:7878")) - .serve(router) - .await; + println!("Listening on http://localhost:{}", port); + + let router = Router::new() + .hoop(cors_handler) + .push(Router::with_path("/").get(info)) + .push(Router::with_path("/w/").get(get_world)) + .push(Router::with_path("/m/").get(get_world_multi)); + Server::new(TcpListener::bind(&addr)).serve(router).await; }