From 16bd7be775613d85f2d1a753701d0316aabe0cbc Mon Sep 17 00:00:00 2001 From: Mettwasser <68561341+Mettwasser@users.noreply.github.com> Date: Sat, 2 Mar 2024 22:05:03 +0100 Subject: [PATCH] feat: Added methods to make update listeners onto the client BREAKING CHANGE: Deprecated warframe-macros and changed project structure --- .releaserc.yaml | 2 +- Cargo.toml | 38 +- .../Cargo.toml | 7 +- examples/listen_to_nested_updates/src/main.rs | 29 ++ .../Cargo.toml | 7 +- examples/listen_to_updates/src/main.rs | 20 + .../Cargo.toml | 7 +- .../listen_to_updates_with_state/src/main.rs | 36 ++ examples/listener_array/src/main.rs | 22 - examples/listener_object/src/main.rs | 21 - .../listener_object_with_state/src/main.rs | 38 -- examples/simple/Cargo.toml | 12 - examples/simple/src/main.rs | 18 - release.yml | 119 ----- {warframe.rs/src => src}/lib.rs | 4 - src/worldstate/client.rs | 452 ++++++++++++++++++ {warframe.rs/src => src}/worldstate/error.rs | 0 .../src => src}/worldstate/language.rs | 0 src/worldstate/listener.rs | 118 +++++ {warframe.rs/src => src}/worldstate/mod.rs | 6 +- .../src => src}/worldstate/models/alert.rs | 0 .../worldstate/models/arbitration.rs | 0 .../worldstate/models/archon_hunt.rs | 0 .../src => src}/worldstate/models/base.rs | 12 +- .../worldstate/models/cambion_drift.rs | 0 .../src => src}/worldstate/models/cetus.rs | 0 .../models/construction_progress.rs | 0 .../worldstate/models/daily_deal.rs | 0 .../src => src}/worldstate/models/event.rs | 2 +- .../src => src}/worldstate/models/faction.rs | 0 .../src => src}/worldstate/models/fissure.rs | 0 .../worldstate/models/flash_sale.rs | 0 .../worldstate/models/global_upgrades.rs | 2 +- .../src => src}/worldstate/models/invasion.rs | 0 .../src => src}/worldstate/models/macros.rs | 4 +- .../src => src}/worldstate/models/mission.rs | 0 .../worldstate/models/mission_type.rs | 0 .../src => src}/worldstate/models/mod.rs | 4 +- .../src => src}/worldstate/models/news.rs | 0 .../worldstate/models/nightwave.rs | 0 .../worldstate/models/orb_vallis.rs | 0 .../src => src}/worldstate/models/reward.rs | 0 .../worldstate/models/reward_type.rs | 0 .../src => src}/worldstate/models/sortie.rs | 0 .../worldstate/models/steel_path.rs | 0 .../worldstate/models/syndicate.rs | 0 .../worldstate/models/syndicate_mission.rs | 0 .../worldstate/models/void_trader.rs | 0 warframe-macros/Cargo.toml | 21 - warframe-macros/src/lib.rs | 171 ------- warframe.rs/Cargo.toml | 35 -- warframe.rs/src/worldstate/client.rs | 77 --- 52 files changed, 712 insertions(+), 572 deletions(-) rename examples/{listener_object_with_state => listen_to_nested_updates}/Cargo.toml (53%) create mode 100644 examples/listen_to_nested_updates/src/main.rs rename examples/{listener_array => listen_to_updates}/Cargo.toml (54%) create mode 100644 examples/listen_to_updates/src/main.rs rename examples/{listener_object => listen_to_updates_with_state}/Cargo.toml (53%) create mode 100644 examples/listen_to_updates_with_state/src/main.rs delete mode 100644 examples/listener_array/src/main.rs delete mode 100644 examples/listener_object/src/main.rs delete mode 100644 examples/listener_object_with_state/src/main.rs delete mode 100644 examples/simple/Cargo.toml delete mode 100644 examples/simple/src/main.rs delete mode 100644 release.yml rename {warframe.rs/src => src}/lib.rs (88%) create mode 100644 src/worldstate/client.rs rename {warframe.rs/src => src}/worldstate/error.rs (100%) rename {warframe.rs/src => src}/worldstate/language.rs (100%) create mode 100644 src/worldstate/listener.rs rename {warframe.rs/src => src}/worldstate/mod.rs (83%) rename {warframe.rs/src => src}/worldstate/models/alert.rs (100%) rename {warframe.rs/src => src}/worldstate/models/arbitration.rs (100%) rename {warframe.rs/src => src}/worldstate/models/archon_hunt.rs (100%) rename {warframe.rs/src => src}/worldstate/models/base.rs (95%) rename {warframe.rs/src => src}/worldstate/models/cambion_drift.rs (100%) rename {warframe.rs/src => src}/worldstate/models/cetus.rs (100%) rename {warframe.rs/src => src}/worldstate/models/construction_progress.rs (100%) rename {warframe.rs/src => src}/worldstate/models/daily_deal.rs (100%) rename {warframe.rs/src => src}/worldstate/models/event.rs (98%) rename {warframe.rs/src => src}/worldstate/models/faction.rs (100%) rename {warframe.rs/src => src}/worldstate/models/fissure.rs (100%) rename {warframe.rs/src => src}/worldstate/models/flash_sale.rs (100%) rename {warframe.rs/src => src}/worldstate/models/global_upgrades.rs (97%) rename {warframe.rs/src => src}/worldstate/models/invasion.rs (100%) rename {warframe.rs/src => src}/worldstate/models/macros.rs (98%) rename {warframe.rs/src => src}/worldstate/models/mission.rs (100%) rename {warframe.rs/src => src}/worldstate/models/mission_type.rs (100%) rename {warframe.rs/src => src}/worldstate/models/mod.rs (95%) rename {warframe.rs/src => src}/worldstate/models/news.rs (100%) rename {warframe.rs/src => src}/worldstate/models/nightwave.rs (100%) rename {warframe.rs/src => src}/worldstate/models/orb_vallis.rs (100%) rename {warframe.rs/src => src}/worldstate/models/reward.rs (100%) rename {warframe.rs/src => src}/worldstate/models/reward_type.rs (100%) rename {warframe.rs/src => src}/worldstate/models/sortie.rs (100%) rename {warframe.rs/src => src}/worldstate/models/steel_path.rs (100%) rename {warframe.rs/src => src}/worldstate/models/syndicate.rs (100%) rename {warframe.rs/src => src}/worldstate/models/syndicate_mission.rs (100%) rename {warframe.rs/src => src}/worldstate/models/void_trader.rs (100%) delete mode 100644 warframe-macros/Cargo.toml delete mode 100644 warframe-macros/src/lib.rs delete mode 100644 warframe.rs/Cargo.toml delete mode 100644 warframe.rs/src/worldstate/client.rs diff --git a/.releaserc.yaml b/.releaserc.yaml index a00ed99..cb8666d 100644 --- a/.releaserc.yaml +++ b/.releaserc.yaml @@ -6,6 +6,6 @@ plugins: - "@semantic-release-cargo/semantic-release-cargo" - "@semantic-release/github" - - "@semantic-release/git" - - assets: ["CHANGELOG.md", "./warframe.rs/Cargo.toml"] + - assets: ["CHANGELOG.md", "Cargo.toml"] branches: - master diff --git a/Cargo.toml b/Cargo.toml index 9dfb744..a480157 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,2 +1,38 @@ +[package] +name = "warframe" +version = "3.1.1" +edition = "2021" +description = "An async crate to wrap Warframe's Worldstate API." +readme = "./README.md" +documentation = "https://docs.rs/warframe" +homepage = "https://docs.rs/warframe" +repository = "https://github.com/Mettwasser/warframe.rs" +license = "MIT" + + +[features] +default = ["worldstate"] + +worldstate = [] +multilangual = ["worldstate"] +worldstate_listeners = ["worldstate"] +worldstate_full = ["worldstate", "multilangual", "worldstate_listeners"] + +[dev-dependencies] +clippy = "0.0.302" +rustfmt = "0.10.0" + +[dependencies] +tokio = { version = "1.34.0", features = ["full"]} +reqwest = { version = "0.11.22", features = ["json"] } +chrono = { version = "0.4.31", features = ["serde", "clock"] } +serde = { version = "1.0.190", features = ["derive"] } +serde_json = { version = "1.0.108" } +serde_flat_path = "0.1.2" +serde_repr = "0.1.18" +futures = "0.3.30" +log = "0.4.20" +env_logger = "0.11.1" + [workspace] -members = ["warframe.rs", "warframe-macros", "examples/*"] \ No newline at end of file +members = [ "examples/*"] diff --git a/examples/listener_object_with_state/Cargo.toml b/examples/listen_to_nested_updates/Cargo.toml similarity index 53% rename from examples/listener_object_with_state/Cargo.toml rename to examples/listen_to_nested_updates/Cargo.toml index bee845a..05d8eed 100644 --- a/examples/listener_object_with_state/Cargo.toml +++ b/examples/listen_to_nested_updates/Cargo.toml @@ -1,13 +1,12 @@ [package] -name = "example-listener-object" +name = "listen_to_nested_updates" version = "0.1.0" edition = "2021" -publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -warframe = { path = "../../warframe.rs/", features = ["worldstate", "macros"] } +warframe = { path = "../../", features = ["worldstate", "worldstate_listeners"] } +tokio = { version = "1.34.0", features = ["full"] } env_logger = "0.11.1" -tokio = "1.36.0" log = "0.4.20" \ No newline at end of file diff --git a/examples/listen_to_nested_updates/src/main.rs b/examples/listen_to_nested_updates/src/main.rs new file mode 100644 index 0000000..cea21dc --- /dev/null +++ b/examples/listen_to_nested_updates/src/main.rs @@ -0,0 +1,29 @@ +use std::error::Error; + +use warframe::worldstate::{listener::Change, prelude::*}; + +/// This function will be called once a fissure updates. +/// This will send a request to the corresponding endpoint once every 30s +/// and compare the results for changes. +async fn on_fissure_update(fissure: Change<'_, Fissure>) { + match fissure { + Change::Added(fissure) => println!("Fissure ADDED : {fissure:?}"), + Change::Removed(fissure) => println!("Fissure REMOVED : {fissure:?}"), + } +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Logging setup + env_logger::builder() + .filter_level(log::LevelFilter::Debug) + .init(); + + // initialize a client (included in the prelude) + let client = Client::new(); + + // Pass the function to the handler + // (will return a Future) + client.call_on_nested_update(on_fissure_update).await?; + Ok(()) +} diff --git a/examples/listener_array/Cargo.toml b/examples/listen_to_updates/Cargo.toml similarity index 54% rename from examples/listener_array/Cargo.toml rename to examples/listen_to_updates/Cargo.toml index 581d279..7ba3d45 100644 --- a/examples/listener_array/Cargo.toml +++ b/examples/listen_to_updates/Cargo.toml @@ -1,13 +1,12 @@ [package] -name = "example-listener-array" +name = "listen_to_updates" version = "0.1.0" edition = "2021" -publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -warframe = { path = "../../warframe.rs/", features = ["worldstate", "macros"]} +warframe = { path = "../../", features = ["worldstate", "worldstate_listeners"] } +tokio = { version = "1.34.0", features = ["full"] } env_logger = "0.11.1" -tokio = "1.36.0" log = "0.4.20" \ No newline at end of file diff --git a/examples/listen_to_updates/src/main.rs b/examples/listen_to_updates/src/main.rs new file mode 100644 index 0000000..f783d12 --- /dev/null +++ b/examples/listen_to_updates/src/main.rs @@ -0,0 +1,20 @@ +use std::error::Error; + +use warframe::worldstate::prelude::*; + +async fn on_cetus_update(before: &Cetus, after: &Cetus) { + println!("BEFORE : {before:?}"); + println!("AFTER : {after:?}"); +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + env_logger::builder() + .filter_level(log::LevelFilter::Debug) + .init(); + + let client = Client::new(); + + client.call_on_update(on_cetus_update).await?; + Ok(()) +} diff --git a/examples/listener_object/Cargo.toml b/examples/listen_to_updates_with_state/Cargo.toml similarity index 53% rename from examples/listener_object/Cargo.toml rename to examples/listen_to_updates_with_state/Cargo.toml index 42e85c2..7af4d8c 100644 --- a/examples/listener_object/Cargo.toml +++ b/examples/listen_to_updates_with_state/Cargo.toml @@ -1,13 +1,12 @@ [package] -name = "example-listener-object-with-state" +name = "listen_to_updates_with_state" version = "0.1.0" edition = "2021" -publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -warframe = { path = "../../warframe.rs/", features = ["worldstate", "macros"] } +warframe = { path = "../../", features = ["worldstate", "worldstate_listeners"] } +tokio = { version = "1.34.0", features = ["full"] } env_logger = "0.11.1" -tokio = "1.36.0" log = "0.4.20" \ No newline at end of file diff --git a/examples/listen_to_updates_with_state/src/main.rs b/examples/listen_to_updates_with_state/src/main.rs new file mode 100644 index 0000000..8db55e2 --- /dev/null +++ b/examples/listen_to_updates_with_state/src/main.rs @@ -0,0 +1,36 @@ +use std::{error::Error, sync::Arc}; + +use warframe::worldstate::prelude::*; + +// Define some state +#[derive(Debug)] +struct MyState { + _num: i32, + _s: String, +} + +async fn on_cetus_update(state: Arc, before: &Cetus, after: &Cetus) { + println!("STATE : {state:?}"); + println!("BEFORE : {before:?}"); + println!("AFTER : {after:?}"); +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + env_logger::builder() + .filter_level(log::LevelFilter::Debug) + .init(); + + let client = Client::new(); + + // Note that the state will be cloned into the handler, so Arc is preferred + let state = Arc::new(MyState { + _num: 69, + _s: "My ginormous ass".into(), + }); + + client + .call_on_update_with_state(on_cetus_update, state) + .await?; + Ok(()) +} diff --git a/examples/listener_array/src/main.rs b/examples/listener_array/src/main.rs deleted file mode 100644 index 8c50e8b..0000000 --- a/examples/listener_array/src/main.rs +++ /dev/null @@ -1,22 +0,0 @@ -use env_logger::builder as env_builder; -use warframe::worldstate::prelude::*; - -/// Used to "listen" to updates of a Type T that implements `RTArray`. -/// The function gets passed `t: Change<'_, T>` -#[warframe::worldstate::listen_any(Fissure)] -async fn on_fissure_update(fissure: Change<'_, Fissure>) { - match fissure { - // Change is an enum representing whether an item was added or removed - Change::Added(fissure) => println!("A Fissure was just ADDED: {:?}", fissure), - Change::Removed(fissure) => println!("A Fissure was just REMOVED: {:?}", fissure), - } -} - -#[tokio::main] -async fn main() -> Result<(), Box> { - env_builder().filter_level(log::LevelFilter::Debug).init(); - - let client = Client::new(); - on_fissure_update(&client).await.unwrap(); - Ok(()) -} diff --git a/examples/listener_object/src/main.rs b/examples/listener_object/src/main.rs deleted file mode 100644 index 8dc42da..0000000 --- a/examples/listener_object/src/main.rs +++ /dev/null @@ -1,21 +0,0 @@ -use env_logger::builder as env_builder; -use warframe::worldstate::prelude::*; - -/// Used to "listen" to updates of a Type T that implements `RTObject`. -/// The function gets passed `before: &T, after: &T` -#[warframe::worldstate::listen(Cetus)] -async fn on_cetus_update(before: &Cetus, after: &Cetus) { - println!("BEFORE: {:?}", before); - println!("AFTER: {:?}", after); -} - -#[tokio::main] -async fn main() -> Result<(), Box> { - env_builder().filter_level(log::LevelFilter::Debug).init(); - - let client = Client::new(); - // Note: The attribute changes the function signature to accept the client object for ease-of-use. - // However, a proper way to "register" listeners on the client object is planned. - on_cetus_update(&client).await.unwrap(); - Ok(()) -} diff --git a/examples/listener_object_with_state/src/main.rs b/examples/listener_object_with_state/src/main.rs deleted file mode 100644 index b8843a9..0000000 --- a/examples/listener_object_with_state/src/main.rs +++ /dev/null @@ -1,38 +0,0 @@ -//! Read listener_object first -use env_logger::builder as env_builder; -use warframe::worldstate::prelude::*; - -/// Some arbitrary state -#[derive(Debug)] -struct SomeState { - _message: String, -} - -/// States specifiers allow you to pass a state to a handler of any listener. -/// The state is the SECOND parameter (comma-seperated) in listen(T). So listen(T, ) -/// vvvvvvvvv passed here -#[warframe::worldstate::listen(Cetus, SomeState)] -// vvvvv received as first argument -async fn on_cetus_update(state: &SomeState, before: &Cetus, after: &Cetus) { - println!("BEFORE: {:?}", before); - println!("AFTER: {:?}", after); - println!("STATE: {:?}", state); -} - -#[tokio::main] -async fn main() -> Result<(), Box> { - env_builder().filter_level(log::LevelFilter::Debug).init(); - - let client = Client::new(); - - // create the state - let state = SomeState { - _message: "Hello, I'm a state!".into(), - }; - - // Of course, you still need to pass it to the handler - on_cetus_update(&state, &client).await.unwrap(); - Ok(()) -} - -// Final Note: These work the same with listen_any(T, ) diff --git a/examples/simple/Cargo.toml b/examples/simple/Cargo.toml deleted file mode 100644 index 73ada67..0000000 --- a/examples/simple/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "simple-usage" -version = "0.1.0" -edition = "2021" -publish = false - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -warframe = { path = "../../warframe.rs/" } -env_logger = "0.11.1" -tokio = "1.36.0" \ No newline at end of file diff --git a/examples/simple/src/main.rs b/examples/simple/src/main.rs deleted file mode 100644 index 24aa905..0000000 --- a/examples/simple/src/main.rs +++ /dev/null @@ -1,18 +0,0 @@ -//! Fissure has the marker trait `RTArray`, meaning the API -//! responds with a list of Fissure objects. -//! To be able to query them, you use `fetch_arr` instead. -//! This will give you a Vec -use warframe::worldstate::prelude::*; - -#[tokio::main] -async fn main() -> Result<(), ApiError> { - let client = Client::new(); - - match client.fetch_arr::().await { - Ok(fissures) => { - fissures.iter().for_each(|f| println!("{f:?}")); - Ok(()) - } - Err(why) => Err(why), - } -} diff --git a/release.yml b/release.yml deleted file mode 100644 index 54a4199..0000000 --- a/release.yml +++ /dev/null @@ -1,119 +0,0 @@ -on: - push: - branches: - - master - pull_request: - branches: - - master - - -jobs: - build-warframe-macros: - name: Build warframe-macros - runs-on: ubuntu-latest - - defaults: - run: - working-directory: /warframe-macros - - needs: - - get-next-version - - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Install semantic-release-cargo - if: needs.get-next-version.outputs.new-release-published == 'true' - uses: EricCrosson/install-github-release-binary@v2 - with: - targets: semantic-release-cargo/semantic-release-cargo@v2 - - - name: Prepare semantic-release for Rust - if: needs.get-next-version.outputs.new-release-published == 'true' - run: semantic-release-cargo prepare ${{ needs.get-next-version.outputs.new-release-version }} - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@master - with: - toolchain: stable - - - name: Rust Cache - uses: Swatinem/rust-cache@v2 - - - name: Cargo build - run: cargo build --release - - - - name: Semantic Release - id: release - uses: cycjimmy/semantic-release-action@v3.2.0 - env: - GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} - CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} - with: - semantic_version: 18 - extra_plugins: | - @semantic-release/git - @semantic-release/changelog - @semantic-release-cargo/semantic-release-cargo - - get-next-version: - uses: semantic-release-action/next-release-version/.github/workflows/next-release-version.yml@v4 - - build-warframe-rs: - name: Build warframe.rs - runs-on: ubuntu-latest - - defaults: - run: - working-directory: /warframe.rs - - needs: - - 'get-next-version' - - 'build-warframe-macros' - - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Install semantic-release-cargo - if: needs.get-next-version.outputs.new-release-published == 'true' - uses: EricCrosson/install-github-release-binary@v2 - with: - targets: semantic-release-cargo/semantic-release-cargo@v2 - - - name: Prepare semantic-release for Rust - if: needs.get-next-version.outputs.new-release-published == 'true' - run: semantic-release-cargo prepare ${{ needs.get-next-version.outputs.new-release-version }} - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@master - with: - toolchain: stable - - - name: Rust Cache - uses: Swatinem/rust-cache@v2 - - - name: Test Worldstate - run: cargo test -F worldstate - - - name: Test Worldstate with all features - run: cargo test -F worldstate_full - - - name: Cargo build - run: cargo build --release - - - - name: Semantic Release - id: release - uses: cycjimmy/semantic-release-action@v3.2.0 - env: - GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} - CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} - with: - semantic_version: 18 - extra_plugins: | - @semantic-release/git - @semantic-release/changelog - @semantic-release-cargo/semantic-release-cargo \ No newline at end of file diff --git a/warframe.rs/src/lib.rs b/src/lib.rs similarity index 88% rename from warframe.rs/src/lib.rs rename to src/lib.rs index eb575de..82591cf 100644 --- a/warframe.rs/src/lib.rs +++ b/src/lib.rs @@ -1,10 +1,6 @@ #[cfg(feature = "worldstate")] pub mod worldstate; -pub use chrono; -pub use log; -pub use tokio; - pub(crate) mod ws { #[cfg(feature = "multilangual")] pub(crate) use crate::worldstate::language::Language; diff --git a/src/worldstate/client.rs b/src/worldstate/client.rs new file mode 100644 index 0000000..f035da8 --- /dev/null +++ b/src/worldstate/client.rs @@ -0,0 +1,452 @@ +use super::error::ApiError; + +use super::models::base::{Endpoint, Model, RTArray, RTObject}; + +#[derive(Default, Debug, Clone)] +pub struct Client { + session: reqwest::Client, +} + +impl Client { + pub fn new() -> Self { + Default::default() + } +} + +// impl FETCH +impl Client { + pub async fn fetch(&self) -> Result + where + T: Model + Endpoint + RTObject, + { + let response = self.session.get(T::endpoint_en()).send().await.unwrap(); + match response.status().as_u16() { + 200 => Ok(response.json::().await.unwrap()), // unwrap should be safe - the API only responds with a JSON + _code => Err(ApiError::from(response).await), + } + } + + pub async fn fetch_arr(&self) -> Result, ApiError> + where + T: Model + Endpoint + RTArray, + { + let response = self.session.get(T::endpoint_en()).send().await.unwrap(); + match response.status().as_u16() { + 200 => Ok(response.json::>().await.unwrap()), // unwrap should be safe - the API only responds with a JSON + _code => Err(ApiError::from(response).await), + } + } +} + +// impl UPDATE LISTENER +#[cfg(feature = "worldstate_listeners")] +impl Client { + /// Asynchronous method that continuously fetches updates for a given type `T` and invokes a callback function. + /// + /// # Arguments + /// + /// - `callback`: A function that implements the `ListenerCallback` trait and is called with the previous and new values of `T`. + /// + /// # Generic Constraints + /// + /// - `T`: Must implement the `Model`, `Endpoint`, `RTObject`, and `TimedEvent` traits. + /// - `Callback`: Must implement the `ListenerCallback` trait with a lifetime parameter `'any` and type parameter `T`. + /// + /// # Returns + /// + /// - `Result<(), ApiError>`: Returns `Ok(())` if the operation is successful, otherwise returns an `ApiError`. + /// + /// # Example + /// + /// ```rust + ///use std::error::Error; + /// + ///use warframe::worldstate::prelude::*; + /// + ///async fn on_cetus_update(before: &Cetus, after: &Cetus) { + /// println!("BEFORE : {before:?}"); + /// println!("AFTER : {after:?}"); + ///} + /// + ///#[tokio::main] + ///async fn main() -> Result<(), Box> { + /// env_logger::builder() + /// .filter_level(log::LevelFilter::Debug) + /// .init(); + /// + /// let client = Client::new(); + /// + /// client.call_on_update(on_cetus_update); // don't forget to start it as a bg task (or .await it)s + /// Ok(()) + ///} + /// + /// ``` + pub async fn call_on_update(&self, callback: Callback) -> Result<(), ApiError> + where + T: Model + Endpoint + RTObject + crate::worldstate::models::TimedEvent, + for<'any> Callback: crate::worldstate::listener::ListenerCallback<'any, T>, + { + log::debug!("{} (LISTENER) :: Started", std::any::type_name::()); + let mut item = self.fetch::().await?; + + loop { + if item.expiry() <= chrono::offset::Utc::now() { + log::debug!( + "{} (LISTENER) :: Fetching new possible update", + std::any::type_name::() + ); + tokio::time::sleep(std::time::Duration::from_secs(30)).await; + + let new_item = self.fetch::().await?; + + if item.expiry() >= new_item.expiry() { + continue; + } + callback.call(&item, &new_item).await; + item = new_item; + } + + let time_to_sleep = item.expiry() - chrono::offset::Utc::now(); + + log::debug!( + "{} (LISTENER) :: Sleeping {} seconds", + std::any::type_name::(), + time_to_sleep.num_seconds() + ); + tokio::time::sleep(time_to_sleep.to_std().unwrap()).await; + } + } + + /// Asynchronous method that continuously fetches updates for a given type `T` and invokes a callback function. + /// + /// # Arguments + /// + /// - `callback`: A function that implements the `ListenerCallback` trait and is called with the previous and new values of `T`. + /// + /// # Generic Constraints + /// + /// - `T`: Must implement the `Model`, `Endpoint`, `RTObject`, and `TimedEvent` traits. + /// - `Callback`: Must implement the `ListenerCallback` trait with a lifetime parameter `'any` and type parameter `T`. + /// + /// # Returns + /// + /// - `Result<(), ApiError>`: Returns `Ok(())` if the operation is successful, otherwise returns an `ApiError`. + /// + /// # Example + /// + /// ```rust + /// use std::error::Error; + /// + /// use warframe::worldstate::{listener::Change, prelude::*}; + /// + /// /// This function will be called once a fissure updates. + /// /// This will send a request to the corresponding endpoint once every 30s + /// /// and compare the results for changes. + /// async fn on_fissure_update(fissure: Change<'_, Fissure>) { + /// match fissure { + /// Change::Added(fissure) => println!("Fissure ADDED : {fissure:?}"), + /// Change::Removed(fissure) => println!("Fissure REMOVED : {fissure:?}"), + /// } + /// } + /// + /// #[tokio::main] + /// async fn main() -> Result<(), Box> { + /// // Logging setup + /// env_logger::builder() + /// .filter_level(log::LevelFilter::Debug) + /// .init(); + /// + /// // initialize a client (included in the prelude) + /// let client = Client::new(); + /// + /// // Pass the function to the handler + /// // (will return a Future) + /// client.call_on_nested_update(on_fissure_update); // don't forget to start it as a bg task (or .await it) + /// Ok(()) + /// } + /// + /// ``` + pub async fn call_on_nested_update( + &self, + callback: Callback, + ) -> Result<(), ApiError> + where + T: Model + Endpoint + RTArray + crate::worldstate::models::TimedEvent + PartialEq, + for<'any> Callback: crate::worldstate::listener::NestedListenerCallback<'any, T>, + { + log::debug!("{} (LISTENER) :: Started", std::any::type_name::>()); + let mut items = self.fetch_arr::().await?; + + loop { + tokio::time::sleep(std::time::Duration::from_secs(30)).await; + + log::debug!( + "{} (LISTENER) :: Fetching new possible state", + std::any::type_name::>() + ); + let new_items = self.fetch_arr::().await?; + + let diff = crate::worldstate::listener::CrossDiff::new(&items, &new_items); + + let removed_items = diff.removed(); + let added_items = diff.added(); + + if !removed_items.is_empty() && !added_items.is_empty() { + log::debug!( + "{} (LISTENER) :: Found changes, proceeding to call callback with every change", + std::any::type_name::>() + ); + for item in removed_items.into_iter().chain(added_items) { + // call callback fn + callback.call(item).await; + } + items = new_items; + } + } + } +} + +// impl UPDATE LISTENER (with state) +#[cfg(feature = "worldstate_listeners")] +impl Client { + /// Asynchronous method that calls a callback function with state on update. + /// + /// # Arguments + /// + /// - `callback`: A callback function that takes the current item, the new item, and the state as arguments. + /// - `state`: The state object that will be passed to the callback function. + /// + /// # Generic Parameters + /// + /// - `S`: The type of the state object. It must be `Sized`, `Send`, `Sync`, and `Clone`. + /// - `T`: The type of the item. It must implement the `Model`, `Endpoint`, `RTObject`, and `TimedEvent` traits. + /// - `Callback`: The type of the callback function. It must implement the `StatefulListenerCallback` trait with the item type `T` and the state type `S`. + /// + /// # Returns + /// + /// This method returns a `Result` indicating whether the operation was successful or an `ApiError` occurred. + /// The result is `Ok(())` if the operation was successful. + /// + /// # Examples + /// + /// ```rust + /// use std::{error::Error, sync::Arc}; + /// + /// use warframe::worldstate::prelude::*; + /// + /// // Define some state + /// #[derive(Debug)] + /// struct MyState { + /// _num: i32, + /// _s: String, + /// } + /// + /// async fn on_cetus_update(state: Arc, before: &Cetus, after: &Cetus) { + /// println!("STATE : {state:?}"); + /// println!("BEFORE : {before:?}"); + /// println!("AFTER : {after:?}"); + /// } + /// + /// #[tokio::main] + /// async fn main() -> Result<(), Box> { + /// env_logger::builder() + /// .filter_level(log::LevelFilter::Debug) + /// .init(); + /// + /// let client = Client::new(); + /// + /// // Note that the state will be cloned into the handler, so Arc is preferred + /// let state = Arc::new(MyState { + /// _num: 69, + /// _s: "My ginormous ass".into(), + /// }); + /// + /// client + /// .call_on_update_with_state(on_cetus_update, state); // don't forget to start it as a bg task (or .await it) + /// Ok(()) + /// } + /// ``` + pub async fn call_on_update_with_state( + &self, + callback: Callback, + state: S, + ) -> Result<(), ApiError> + where + S: Sized + Send + Sync + Clone, + T: Model + Endpoint + RTObject + crate::worldstate::models::TimedEvent, + for<'any> Callback: crate::worldstate::listener::StatefulListenerCallback<'any, T, S>, + { + let mut item = self.fetch::().await?; + + loop { + if item.expiry() <= chrono::offset::Utc::now() { + log::debug!( + "{} (LISTENER) :: Fetching possible update", + std::any::type_name::() + ); + tokio::time::sleep(std::time::Duration::from_secs(30)).await; + + let new_item = self.fetch::().await?; + + if item.expiry() >= new_item.expiry() { + continue; + } + callback + .call_with_state(state.clone(), &item, &new_item) + .await; + item = new_item; + } + + let time_to_sleep = item.expiry() - chrono::offset::Utc::now(); + + log::debug!( + "{} (LISTENER) :: Sleeping {} seconds", + std::any::type_name::(), + time_to_sleep.num_seconds() + ); + tokio::time::sleep(time_to_sleep.to_std().unwrap()).await; + } + } + + /// Asynchronous method that calls a callback function on nested updates with a given state. + /// + /// # Arguments + /// + /// * `callback` - The callback function to be called on each change. + /// * `state` - The state to be passed to the callback function. + /// + /// # Generic Constraints + /// + /// * `S` - The type of the state, which must be `Sized`, `Send`, `Sync`, and `Clone`. + /// * `T` - The type of the items, which must implement the `Model`, `Endpoint`, `RTArray`, `TimedEvent`, and `PartialEq` traits. + /// * `Callback` - The type of the callback function, which must implement the `StatefulNestedListenerCallback` trait. + /// + /// # Returns + /// + /// Returns `Ok(())` if the callback function is successfully called on each change, or an `ApiError` if an error occurs. + /// + /// # Example + /// + /// ```rust + /// use std::{error::Error, sync::Arc}; + /// + /// use warframe::worldstate::{listener::Change, prelude::*}; + /// + /// // Define some state + /// #[derive(Debug)] + /// struct MyState { + /// _num: i32, + /// _s: String, + /// } + /// + /// async fn on_fissure_update(state: Arc, fissure: Change<'_, Fissure>) { + /// println!("STATE : {state:?}"); + /// match fissure { + /// Change::Added(f) => println!("FISSURE ADDED : {f:?}"), + /// Change::Removed(f) => println!("FISSURE REMOVED : {f:?}"), + /// } + /// } + /// + /// #[tokio::main] + /// async fn main() -> Result<(), Box> { + /// env_logger::builder() + /// .filter_level(log::LevelFilter::Debug) + /// .init(); + /// + /// let client = Client::new(); + /// + /// // Note that the state will be cloned into the handler, so Arc is preferred + /// let state = Arc::new(MyState { + /// _num: 69, + /// _s: "My ginormous ass".into(), + /// }); + /// + /// client + /// .call_on_nested_update_with_state(on_fissure_update, state); // don't forget to start it as a bg task (or .await it) + /// Ok(()) + /// } + /// ``` + pub async fn call_on_nested_update_with_state( + &self, + callback: Callback, + state: S, + ) -> Result<(), ApiError> + where + S: Sized + Send + Sync + Clone, + T: Model + Endpoint + RTArray + crate::worldstate::models::TimedEvent + PartialEq, + for<'any> Callback: crate::worldstate::listener::StatefulNestedListenerCallback<'any, T, S>, + { + log::debug!("{} (LISTENER) :: Started", std::any::type_name::>()); + let mut items = self.fetch_arr::().await?; + + loop { + tokio::time::sleep(std::time::Duration::from_secs(30)).await; + + log::debug!( + "{} (LISTENER) :: Fetching new possible state", + std::any::type_name::>() + ); + let new_items = self.fetch_arr::().await?; + + let diff = crate::worldstate::listener::CrossDiff::new(&items, &new_items); + + let removed_items = diff.removed(); + let added_items = diff.added(); + + if !removed_items.is_empty() && !added_items.is_empty() { + log::debug!( + "{} (LISTENER) :: Found changes, proceeding to call callback with every change", + std::any::type_name::>() + ); + for item in removed_items.into_iter().chain(added_items) { + // call callback fn + callback.call_with_state(state.clone(), item).await; + } + items = new_items; + } + } + } +} + +// impl FETCH (with language) +#[cfg(feature = "multilangual")] +impl Client { + pub async fn fetch_using_lang( + &self, + language: super::language::Language, + ) -> Result + where + T: Model + Endpoint + RTObject, + { + let response = self + .session + .get(T::endpoint(language)) + .send() + .await + .unwrap(); + match response.status().as_u16() { + 200 => Ok(response.json::().await.unwrap()), // unwrap should be safe - the API only responds with a JSON + _code => Err(ApiError::from(response).await), + } + } + + pub async fn fetch_arr_using_lang( + &self, + language: super::language::Language, + ) -> Result, ApiError> + where + T: Model + Endpoint + RTArray, + { + let response = self + .session + .get(T::endpoint(language)) + .send() + .await + .unwrap(); + + match response.status().as_u16() { + 200 => Ok(response.json::>().await.unwrap()), // unwrap should be safe - the API only responds with a JSON + _code => Err(ApiError::from(response).await), + } + } +} diff --git a/warframe.rs/src/worldstate/error.rs b/src/worldstate/error.rs similarity index 100% rename from warframe.rs/src/worldstate/error.rs rename to src/worldstate/error.rs diff --git a/warframe.rs/src/worldstate/language.rs b/src/worldstate/language.rs similarity index 100% rename from warframe.rs/src/worldstate/language.rs rename to src/worldstate/language.rs diff --git a/src/worldstate/listener.rs b/src/worldstate/listener.rs new file mode 100644 index 0000000..7c5827d --- /dev/null +++ b/src/worldstate/listener.rs @@ -0,0 +1,118 @@ +use std::future::Future; + +use super::models::{Endpoint, Model, RTArray, RTObject, TimedEvent}; + +pub enum Change<'a, T> { + Added(&'a T), + Removed(&'a T), +} + +// ---------- +pub trait ListenerCallback<'a, T, S = ()> +where + T: Sized + Endpoint + RTObject + Model + 'a, + S: Sized + Send + Sync, +{ + fn call(&self, before: &'a T, after: &'a T) -> impl Future + 'a; +} + +impl<'a, T, Fut, Func> ListenerCallback<'a, T> for Func +where + T: Sized + Endpoint + RTObject + Model + 'a, + Fut: Future + 'a, + Func: Fn(&'a T, &'a T) -> Fut, +{ + fn call(&self, before: &'a T, after: &'a T) -> impl Future + 'a { + self(before, after) + } +} + +pub trait NestedListenerCallback<'a, T> +where + T: Sized + Endpoint + RTArray + TimedEvent + Model + 'a, +{ + fn call(&self, change: Change<'a, T>) -> impl Future + 'a; +} + +impl<'a, T, Fut, Func> NestedListenerCallback<'a, T> for Func +where + T: Sized + Endpoint + RTArray + TimedEvent + Model + 'a, + Fut: Future + 'a, + Func: Fn(Change<'a, T>) -> Fut, +{ + fn call(&self, change: Change<'a, T>) -> impl Future + 'a { + self(change) + } +} + +// --------- STATEFUL CALLBACKS +pub trait StatefulListenerCallback<'a, T, S> +where + T: Sized + Endpoint + RTObject + Model + 'a, + S: Sized + Send + Sync, +{ + fn call_with_state(&self, state: S, before: &'a T, after: &'a T) -> impl Future + 'a; +} + +impl<'a, T, Fut, Func, S> StatefulListenerCallback<'a, T, S> for Func +where + T: Sized + Endpoint + RTObject + Model + 'a, + S: Sized + Send + Sync, + Fut: Future + 'a, + Func: Fn(S, &'a T, &'a T) -> Fut, +{ + fn call_with_state(&self, state: S, before: &'a T, after: &'a T) -> impl Future + 'a { + self(state, before, after) + } +} + +pub trait StatefulNestedListenerCallback<'a, T, S> +where + T: Sized + Endpoint + RTArray + TimedEvent + Model + 'a, +{ + fn call_with_state(&self, state: S, change: Change<'a, T>) -> impl Future + 'a; +} + +impl<'a, T, Fut, Func, S> StatefulNestedListenerCallback<'a, T, S> for Func +where + T: Sized + Endpoint + RTArray + TimedEvent + Model + 'a, + Fut: Future + 'a, + Func: Fn(S, Change<'a, T>) -> Fut, +{ + fn call_with_state(&self, state: S, change: Change<'a, T>) -> impl Future + 'a { + self(state, change) + } +} + +pub struct CrossDiff<'a, T> +where + T: Model + Endpoint + RTArray, +{ + current: &'a [T], + incoming: &'a [T], +} + +impl<'a, T> CrossDiff<'a, T> +where + T: Model + Endpoint + RTArray, +{ + pub fn new(current: &'a [T], incoming: &'a [T]) -> Self { + Self { current, incoming } + } + + pub fn removed(&self) -> Vec> { + self.current + .iter() + .filter(|&item| !self.incoming.contains(item)) + .map(Change::Removed) + .collect() + } + + pub fn added(&self) -> Vec> { + self.incoming + .iter() + .filter(|&item| !self.incoming.contains(item)) + .map(Change::Added) + .collect() + } +} diff --git a/warframe.rs/src/worldstate/mod.rs b/src/worldstate/mod.rs similarity index 83% rename from warframe.rs/src/worldstate/mod.rs rename to src/worldstate/mod.rs index d5aeb0d..fd5e4a3 100644 --- a/warframe.rs/src/worldstate/mod.rs +++ b/src/worldstate/mod.rs @@ -2,12 +2,12 @@ pub mod client; pub mod error; pub mod models; -#[cfg(feature = "macros")] -pub use warframe_macros::{listen, listen_any}; - #[cfg(feature = "multilangual")] pub mod language; +#[cfg(feature = "worldstate_listeners")] +pub mod listener; + pub mod prelude { pub use crate::worldstate::client::Client; pub use crate::worldstate::error::ApiError; diff --git a/warframe.rs/src/worldstate/models/alert.rs b/src/worldstate/models/alert.rs similarity index 100% rename from warframe.rs/src/worldstate/models/alert.rs rename to src/worldstate/models/alert.rs diff --git a/warframe.rs/src/worldstate/models/arbitration.rs b/src/worldstate/models/arbitration.rs similarity index 100% rename from warframe.rs/src/worldstate/models/arbitration.rs rename to src/worldstate/models/arbitration.rs diff --git a/warframe.rs/src/worldstate/models/archon_hunt.rs b/src/worldstate/models/archon_hunt.rs similarity index 100% rename from warframe.rs/src/worldstate/models/archon_hunt.rs rename to src/worldstate/models/archon_hunt.rs diff --git a/warframe.rs/src/worldstate/models/base.rs b/src/worldstate/models/base.rs similarity index 95% rename from warframe.rs/src/worldstate/models/base.rs rename to src/worldstate/models/base.rs index a3e0981..e36aaca 100644 --- a/warframe.rs/src/worldstate/models/base.rs +++ b/src/worldstate/models/base.rs @@ -12,7 +12,7 @@ pub trait Endpoint { } /// The base trait implemented by every Model in the API. -pub trait Model: DeserializeOwned { +pub trait Model: DeserializeOwned + PartialEq { fn from_str(raw_json: &str) -> Result { serde_json::from_str::(raw_json) } @@ -122,16 +122,6 @@ pub trait Opposite { fn opposite(&self) -> Self; } -#[cfg(feature = "macros")] -pub mod macro_features { - use super::*; - - pub enum Change<'a, T: 'a + Model + RTArray> { - Added(&'a T), - Removed(&'a T), - } -} - #[cfg(feature = "macros")] pub use macro_features::*; diff --git a/warframe.rs/src/worldstate/models/cambion_drift.rs b/src/worldstate/models/cambion_drift.rs similarity index 100% rename from warframe.rs/src/worldstate/models/cambion_drift.rs rename to src/worldstate/models/cambion_drift.rs diff --git a/warframe.rs/src/worldstate/models/cetus.rs b/src/worldstate/models/cetus.rs similarity index 100% rename from warframe.rs/src/worldstate/models/cetus.rs rename to src/worldstate/models/cetus.rs diff --git a/warframe.rs/src/worldstate/models/construction_progress.rs b/src/worldstate/models/construction_progress.rs similarity index 100% rename from warframe.rs/src/worldstate/models/construction_progress.rs rename to src/worldstate/models/construction_progress.rs diff --git a/warframe.rs/src/worldstate/models/daily_deal.rs b/src/worldstate/models/daily_deal.rs similarity index 100% rename from warframe.rs/src/worldstate/models/daily_deal.rs rename to src/worldstate/models/daily_deal.rs diff --git a/warframe.rs/src/worldstate/models/event.rs b/src/worldstate/models/event.rs similarity index 98% rename from warframe.rs/src/worldstate/models/event.rs rename to src/worldstate/models/event.rs index ef6e108..c63c264 100644 --- a/warframe.rs/src/worldstate/models/event.rs +++ b/src/worldstate/models/event.rs @@ -43,7 +43,7 @@ model_builder! { pub rewards: Vec, :"Amount of health remaining for the target" - pub health: Option, + pub health: Option, :"The associated Syndicate" pub affiliated_with: Option, diff --git a/warframe.rs/src/worldstate/models/faction.rs b/src/worldstate/models/faction.rs similarity index 100% rename from warframe.rs/src/worldstate/models/faction.rs rename to src/worldstate/models/faction.rs diff --git a/warframe.rs/src/worldstate/models/fissure.rs b/src/worldstate/models/fissure.rs similarity index 100% rename from warframe.rs/src/worldstate/models/fissure.rs rename to src/worldstate/models/fissure.rs diff --git a/warframe.rs/src/worldstate/models/flash_sale.rs b/src/worldstate/models/flash_sale.rs similarity index 100% rename from warframe.rs/src/worldstate/models/flash_sale.rs rename to src/worldstate/models/flash_sale.rs diff --git a/warframe.rs/src/worldstate/models/global_upgrades.rs b/src/worldstate/models/global_upgrades.rs similarity index 97% rename from warframe.rs/src/worldstate/models/global_upgrades.rs rename to src/worldstate/models/global_upgrades.rs index dfbfb68..1dcb8ca 100644 --- a/warframe.rs/src/worldstate/models/global_upgrades.rs +++ b/src/worldstate/models/global_upgrades.rs @@ -26,7 +26,7 @@ model_builder! { pub operation_symbol: String, :"Value corresponding to performing the operation" - pub upgrade_operation_value: String, + pub upgrade_operation_value: i32, :"Whether the upgrade has expired" pub expired: bool, diff --git a/warframe.rs/src/worldstate/models/invasion.rs b/src/worldstate/models/invasion.rs similarity index 100% rename from warframe.rs/src/worldstate/models/invasion.rs rename to src/worldstate/models/invasion.rs diff --git a/warframe.rs/src/worldstate/models/macros.rs b/src/worldstate/models/macros.rs similarity index 98% rename from warframe.rs/src/worldstate/models/macros.rs rename to src/worldstate/models/macros.rs index 94047e7..c202107 100644 --- a/warframe.rs/src/worldstate/models/macros.rs +++ b/src/worldstate/models/macros.rs @@ -69,13 +69,13 @@ macro_rules! impl_endpoint { ($struct_name:ident, $endpoint:literal) => { impl $crate::ws::Endpoint for $struct_name { fn endpoint_en() -> &'static str { - concat!("https://api.warframestat.us/pc", $endpoint, "?language=en") + concat!("https://api.warframestat.us/pc", $endpoint, "/?language=en") } #[cfg(feature = "multilangual")] fn endpoint(language: $crate::ws::Language) -> String { format!( - "https://api.warframestat.us/pc{}?language={}", + "https://api.warframestat.us/pc{}/?language={}", $endpoint, String::from(language) ) diff --git a/warframe.rs/src/worldstate/models/mission.rs b/src/worldstate/models/mission.rs similarity index 100% rename from warframe.rs/src/worldstate/models/mission.rs rename to src/worldstate/models/mission.rs diff --git a/warframe.rs/src/worldstate/models/mission_type.rs b/src/worldstate/models/mission_type.rs similarity index 100% rename from warframe.rs/src/worldstate/models/mission_type.rs rename to src/worldstate/models/mission_type.rs diff --git a/warframe.rs/src/worldstate/models/mod.rs b/src/worldstate/models/mod.rs similarity index 95% rename from warframe.rs/src/worldstate/models/mod.rs rename to src/worldstate/models/mod.rs index 763bbdb..7a07a75 100644 --- a/warframe.rs/src/worldstate/models/mod.rs +++ b/src/worldstate/models/mod.rs @@ -10,7 +10,7 @@ mod event; mod faction; mod fissure; mod flash_sale; -mod global_upgrades; +// mod global_upgrades; mod invasion; pub(crate) mod macros; mod mission; @@ -42,7 +42,7 @@ pub use cetus::{Cetus, CetusState}; pub use faction::Faction; pub use fissure::{Fissure, Tier}; pub use flash_sale::FlashSale; -pub use global_upgrades::GlobalUpgrade; +// pub use global_upgrades::GlobalUpgrade; pub use invasion::{Invasion, InvasionMember}; pub use mission::Mission; pub use mission_type::MissionType; diff --git a/warframe.rs/src/worldstate/models/news.rs b/src/worldstate/models/news.rs similarity index 100% rename from warframe.rs/src/worldstate/models/news.rs rename to src/worldstate/models/news.rs diff --git a/warframe.rs/src/worldstate/models/nightwave.rs b/src/worldstate/models/nightwave.rs similarity index 100% rename from warframe.rs/src/worldstate/models/nightwave.rs rename to src/worldstate/models/nightwave.rs diff --git a/warframe.rs/src/worldstate/models/orb_vallis.rs b/src/worldstate/models/orb_vallis.rs similarity index 100% rename from warframe.rs/src/worldstate/models/orb_vallis.rs rename to src/worldstate/models/orb_vallis.rs diff --git a/warframe.rs/src/worldstate/models/reward.rs b/src/worldstate/models/reward.rs similarity index 100% rename from warframe.rs/src/worldstate/models/reward.rs rename to src/worldstate/models/reward.rs diff --git a/warframe.rs/src/worldstate/models/reward_type.rs b/src/worldstate/models/reward_type.rs similarity index 100% rename from warframe.rs/src/worldstate/models/reward_type.rs rename to src/worldstate/models/reward_type.rs diff --git a/warframe.rs/src/worldstate/models/sortie.rs b/src/worldstate/models/sortie.rs similarity index 100% rename from warframe.rs/src/worldstate/models/sortie.rs rename to src/worldstate/models/sortie.rs diff --git a/warframe.rs/src/worldstate/models/steel_path.rs b/src/worldstate/models/steel_path.rs similarity index 100% rename from warframe.rs/src/worldstate/models/steel_path.rs rename to src/worldstate/models/steel_path.rs diff --git a/warframe.rs/src/worldstate/models/syndicate.rs b/src/worldstate/models/syndicate.rs similarity index 100% rename from warframe.rs/src/worldstate/models/syndicate.rs rename to src/worldstate/models/syndicate.rs diff --git a/warframe.rs/src/worldstate/models/syndicate_mission.rs b/src/worldstate/models/syndicate_mission.rs similarity index 100% rename from warframe.rs/src/worldstate/models/syndicate_mission.rs rename to src/worldstate/models/syndicate_mission.rs diff --git a/warframe.rs/src/worldstate/models/void_trader.rs b/src/worldstate/models/void_trader.rs similarity index 100% rename from warframe.rs/src/worldstate/models/void_trader.rs rename to src/worldstate/models/void_trader.rs diff --git a/warframe-macros/Cargo.toml b/warframe-macros/Cargo.toml deleted file mode 100644 index d49a2ac..0000000 --- a/warframe-macros/Cargo.toml +++ /dev/null @@ -1,21 +0,0 @@ -[package] -name = "warframe-macros" -version = "0.2.0" -edition = "2021" -description = "warframe.rs' macros" -documentation = "https://docs.rs/warframe" -homepage = "https://docs.rs/warframe" -repository = "https://github.com/Mettwasser/warframe.rs" -license = "MIT" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[lib] -proc-macro = true - -[dependencies] -quote = "1.0.35" -proc-macro2 = "1.0.78" -darling = "0.20.4" -syn = "2.0.48" -log = "0.4.20" diff --git a/warframe-macros/src/lib.rs b/warframe-macros/src/lib.rs deleted file mode 100644 index a7c7f89..0000000 --- a/warframe-macros/src/lib.rs +++ /dev/null @@ -1,171 +0,0 @@ -use std::error::Error; - -use darling::FromMeta; -use proc_macro::TokenStream; -use proc_macro2::{Ident, TokenStream as TokenStream2}; -use quote::quote; -use syn::{ - parse::{Parse, ParseStream}, - parse_macro_input, - token::Comma, - ItemFn, -}; - -fn impl_listen( - model_ident: Ident, - state_ident: Option, - item: ItemFn, -) -> Result> { - let model_name = model_ident.to_string(); - - let original_fn_name = item.sig.ident.clone(); - - let mut item_clone = item.clone(); - item_clone.sig.ident = Ident::from_string(&format!("_{}", item.sig.ident))?; - - let item_sig = Ident::from_string(&format!("_{}", item.sig.ident))?; - - let state: Option = state_ident - .clone() - .map(|ident| format!("state: &{},", ident).parse().unwrap()); - - let state_arg: Option = state_ident.map(|_| "&state,".parse().unwrap()); - - Ok(quote! { - async fn #original_fn_name( - #state - client: &warframe::worldstate::client::Client, - ) -> Result<(), warframe::worldstate::error::ApiError> { - use warframe::worldstate::prelude::*; - use log::debug; - let mut item = client.fetch::<#model_ident>().await?; - - #item_clone - - - loop { - if item.expiry() <= warframe::chrono::offset::Utc::now() { - debug!("{} :: Looking for state change from the API", #model_name); - warframe::tokio::time::sleep(std::time::Duration::from_secs(30)).await; - - let new_item = client.fetch::<#model_ident>().await?; - - if item.expiry() >= new_item.expiry() { - continue; - } else { - // call callback fn - #item_sig(#state_arg &item, &new_item).await; - item = new_item; - } - } - - let time_to_sleep = item.expiry() - warframe::chrono::offset::Utc::now(); - - debug!( - "{} :: Sleeping {} seconds", - #model_name, - time_to_sleep.num_seconds() - ); - warframe::tokio::time::sleep(time_to_sleep.to_std().unwrap()).await; - } - } - }) -} - -#[proc_macro_attribute] -pub fn listen(args: TokenStream, input: TokenStream) -> TokenStream { - let ParsedIdents { first, second } = parse_macro_input!(args as ParsedIdents); - - impl_listen(first, second, parse_macro_input!(input)) - .unwrap() - .into() -} - -struct ParsedIdents { - first: Ident, - second: Option, -} - -impl Parse for ParsedIdents { - fn parse(input: ParseStream) -> syn::Result { - let first = input.parse()?; - let mut second = None; - if input.peek(Comma) { - let _: Comma = input.parse()?; - second = Some(input.parse()?); - } - - Ok(ParsedIdents { first, second }) - } -} - -fn impl_listen_any( - model_ident: Ident, - state_ident: Option, - item: ItemFn, -) -> Result> { - let model_name = model_ident.to_string(); - - let original_fn_name = item.sig.ident.clone(); - - let mut item_clone = item.clone(); - item_clone.sig.ident = Ident::from_string(&format!("_{}", item.sig.ident)).unwrap(); - let item_sig = Ident::from_string(&format!("_{}", item.sig.ident)).unwrap(); - - let state: Option = state_ident - .clone() - .map(|ident| format!("state: &{},", ident).parse().unwrap()); - - let state_arg: Option = state_ident.map(|_| "&state,".parse().unwrap()); - - Ok(quote! { - async fn #original_fn_name( - #state - client: &warframe::worldstate::client::Client, - ) -> Result<(), warframe::worldstate::error::ApiError> { - use log::debug; - use warframe::worldstate::prelude::*; - - #item_clone - - let mut items = client.fetch_arr::<#model_ident>().await?; - - loop { - debug!("{} :: Fetching for changes", #model_name); - warframe::tokio::time::sleep(std::time::Duration::from_secs(60)).await; - - let new_items = client.fetch_arr::<#model_ident>().await?; - - let removed_items: Vec<_> = items - .iter() - .filter(|&item| !new_items.contains(item)) - .map(warframe::worldstate::models::Change::Removed) - .collect(); - - let added_items: Vec<_> = new_items - .iter() - .filter(|&item| !items.contains(item)) - .map(warframe::worldstate::models::Change::Added) - .collect(); - - if !removed_items.is_empty() && !added_items.is_empty() { - debug!("{} :: Found changes", #model_name); - for item in removed_items.into_iter().chain(added_items) { - // call callback fn - #item_sig(#state_arg item).await; - } - items = new_items; - } - } - } - }) -} - -#[proc_macro_attribute] -pub fn listen_any(args: TokenStream, input: TokenStream) -> TokenStream { - let ParsedIdents { first, second } = parse_macro_input!(args as ParsedIdents); - - impl_listen_any(first, second, parse_macro_input!(input)) - .unwrap() - .into() -} diff --git a/warframe.rs/Cargo.toml b/warframe.rs/Cargo.toml deleted file mode 100644 index d74e5a0..0000000 --- a/warframe.rs/Cargo.toml +++ /dev/null @@ -1,35 +0,0 @@ -[package] -name = "warframe" -version = "3.1.1" -edition = "2021" -description = "An async crate to wrap Warframe's Worldstate API." -readme = "../README.md" -documentation = "https://docs.rs/warframe" -homepage = "https://docs.rs/warframe" -repository = "https://github.com/Mettwasser/warframe.rs" -license = "MIT" - -[features] -worldstate = [] -default = ["worldstate"] -worldstate_full = ["worldstate", "multilangual", "macros"] -multilangual = ["worldstate"] -macros = ["worldstate"] - -[dev-dependencies] -clippy = "0.0.302" -rustfmt = "0.10.0" - -[dependencies] -tokio = { version = "1.34.0", features = ["full"] } -reqwest = { version = "0.11.22", features = ["json"] } -chrono = { version = "0.4.31", features = ["serde", "clock"] } -serde = { version = "1.0.190", features = ["derive"] } -serde_json = { version = "1.0.108" } -serde_flat_path = "0.1.2" -serde_repr = "0.1.18" -async-tungstenite = { version = "0.24.0", features = ["tokio-native-tls"] } -futures = "0.3.30" -warframe-macros = { version = "3.1.1", path = "../warframe-macros" } -log = "0.4.20" -env_logger = "0.11.1" diff --git a/warframe.rs/src/worldstate/client.rs b/warframe.rs/src/worldstate/client.rs deleted file mode 100644 index 17a97f1..0000000 --- a/warframe.rs/src/worldstate/client.rs +++ /dev/null @@ -1,77 +0,0 @@ -use super::error::ApiError; -use super::models::base::{Endpoint, Model, RTArray, RTObject}; - -#[derive(Default)] -pub struct Client { - session: reqwest::Client, -} - -impl Client { - pub fn new() -> Self { - Default::default() - } - - pub async fn fetch(&self) -> Result - where - T: Model + Endpoint + RTObject, - { - let response = self.session.get(T::endpoint_en()).send().await.unwrap(); - match response.status().as_u16() { - 200 => Ok(response.json::().await.unwrap()), // unwrap should be safe - the API only responds with a JSON - _code => Err(ApiError::from(response).await), - } - } - - pub async fn fetch_arr(&self) -> Result, ApiError> - where - T: Model + Endpoint + RTArray, - { - let response = self.session.get(T::endpoint_en()).send().await.unwrap(); - match response.status().as_u16() { - 200 => Ok(response.json::>().await.unwrap()), // unwrap should be safe - the API only responds with a JSON - _code => Err(ApiError::from(response).await), - } - } -} - -#[cfg(feature = "multilangual")] -impl Client { - pub async fn fetch_using_lang( - &self, - language: super::language::Language, - ) -> Result - where - T: Model + Endpoint + RTObject, - { - let response = self - .session - .get(T::endpoint(language)) - .send() - .await - .unwrap(); - match response.status().as_u16() { - 200 => Ok(response.json::().await.unwrap()), // unwrap should be safe - the API only responds with a JSON - _code => Err(ApiError::from(response).await), - } - } - - pub async fn fetch_arr_using_lang( - &self, - language: super::language::Language, - ) -> Result, ApiError> - where - T: Model + Endpoint + RTArray, - { - let response = self - .session - .get(T::endpoint(language)) - .send() - .await - .unwrap(); - - match response.status().as_u16() { - 200 => Ok(response.json::>().await.unwrap()), // unwrap should be safe - the API only responds with a JSON - _code => Err(ApiError::from(response).await), - } - } -}