psn_api_rs/
lib.rs

1//! # A simple PSN API wrapper.
2//! It uses an async http client(hyper::Client in this case) to communicate wih the official PSN API.
3//!
4//! Some basics:
5//! The crate use `npsso` code to login in to PSN Network and get a pair of `access_token` and `refresh_token` in response.
6//! [How to obtain uuid and two_step tokens](https://tusticles.com/psn-php/first_login.html)
7//! The `access_token` last about an hour before expire and it's needed to call most other PSN APIs(The PSN store API doesn't need any token to access though).
8//! The `refresh_token` last much longer and it's used to generate a new access_token after/before it is expired.
9//!
10//! * Note:
11//! There is a rate limiter for the official PSN API so better not make lots of calls in short time.
12//! The proxy example (The best practice to use this libaray) shows how to make high concurrency possible and combat the rate limiter effectivly.
13//!
14//! # Basic Example:
15//!```no_run
16//!use psn_api_rs::{psn::PSN, types::PSNInner, traits::PSNRequest, models::PSNUser};
17//!
18//!#[tokio::main]
19//!async fn main() -> std::io::Result<()> {
20//!    let refresh_token = String::from("your refresh token");
21//!    let npsso = String::from("your npsso");
22//!
23//!    let client = PSN::new_client().expect("Failed to build http client");
24//!
25//!    // construct a PSNInner object,add credentials and call auth to generate tokens.
26//!    let mut psn_inner = PSNInner::new();
27//!    psn_inner.set_region("us".to_owned()) // <- set to a psn region server suit your case. you can leave it as default which is hk
28//!            .set_lang("en".to_owned()) // <- set to a language you want the response to be. default is en
29//!            .set_self_online_id(String::from("Your Login account PSN online_id")) // <- this is used to generate new message thread. safe to leave unset if you don't need to send any PSN message.
30//!            .add_refresh_token(refresh_token) // <- If refresh_token is provided then it's safe to ignore add_npsso and call .auth() directly.
31//!            .add_npsso(npsso); // <- npsso is used only when refresh_token is not working or not provided.
32//!
33//!    psn_inner = psn_inner
34//!            .auth()
35//!            .await
36//!            .unwrap_or_else(|e| panic!("{:?}", e));
37//!
38//!    println!(
39//!        "Authentication Success! These are your info from PSN network: \r\n{:#?} ",
40//!        psn_inner
41//!    );
42//!
43//!    let user = psn_inner
44//!            .get_profile::<PSNUser>(&client, "Hakoom")
45//!            .await
46//!            .unwrap_or_else(|e| panic!("{:?}", e));
47//!
48//!    println!(
49//!        "Example finished. Got user info : \r\n{:#?}",
50//!        user
51//!    );
52//!
53//!    Ok(())
54//!    // psn struct is dropped at this point so it's better to store your access_token and refresh_token here to make them reusable.
55//!}
56//!```
57
58#[macro_use]
59extern crate serde_derive;
60
61pub mod metas;
62pub mod models;
63pub mod traits;
64pub mod types;
65
66mod private_model;
67
68#[cfg(feature = "default")]
69pub mod psn {
70    use crossbeam_queue::SegQueue;
71    use derive_more::Display;
72    use reqwest::{Client, ClientBuilder, Error, Proxy};
73    use serde::de::DeserializeOwned;
74    use tang_rs::{Builder, Manager, ManagerFuture, Pool, PoolRef};
75
76    use crate::traits::PSNRequest;
77    use crate::types::PSNInner;
78
79    #[derive(Debug, Clone)]
80    pub struct PSN {
81        inner: Pool<PSNInnerManager>,
82        client: Client,
83        proxy_pool: Option<Pool<ProxyPoolManager>>,
84    }
85
86    /// You can override `PSNRequest` trait to impl your own error type.
87    #[derive(Debug, Display)]
88    pub enum PSNError {
89        #[display(fmt = "No http client is available and/or new client can't be made.")]
90        NoClient,
91        #[display(fmt = "No psn object is available")]
92        NoPSNInner,
93        #[display(fmt = "Failed to login in to PSN")]
94        AuthenticationFail,
95        #[display(fmt = "Request is timeout")]
96        TimeOut,
97        #[display(fmt = "Error from Reqwest: {}", _0)]
98        FromReqwest(Error),
99        #[display(fmt = "Error from PSN response: {}", _0)]
100        FromPSN(String),
101        #[display(fmt = "Error from Local: {}", _0)]
102        FromStd(std::io::Error),
103    }
104
105    pub struct PSNInnerManager {
106        inner: SegQueue<PSNInner>,
107        client: Client,
108    }
109
110    impl PSNInnerManager {
111        fn new() -> Self {
112            PSNInnerManager {
113                inner: SegQueue::new(),
114                client: ClientBuilder::new()
115                    .build()
116                    .expect("Failed to build http client for PSNInnerManager"),
117            }
118        }
119
120        fn add_psn_inner(&self, psn: PSNInner) {
121            self.inner.push(psn);
122        }
123    }
124
125    impl Manager for PSNInnerManager {
126        type Connection = PSNInner;
127        type Error = PSNError;
128
129        fn connect(&self) -> ManagerFuture<'_, Result<Self::Connection, Self::Error>> {
130            Box::pin(async move { self.inner.pop().map_err(|_| PSNError::NoClient) })
131        }
132
133        fn is_valid<'a>(
134            &'a self,
135            conn: &'a mut Self::Connection,
136        ) -> ManagerFuture<'a, Result<(), Self::Error>> {
137            Box::pin(async move {
138                if conn.should_refresh() {
139                    conn.gen_access_from_refresh(&self.client).await
140                } else {
141                    Ok(())
142                }
143            })
144        }
145
146        fn is_closed(&self, _conn: &mut Self::Connection) -> bool {
147            false
148        }
149    }
150
151    pub struct ProxyPoolManager {
152        proxies: SegQueue<(String, Option<String>, Option<String>)>,
153        marker: &'static str,
154    }
155
156    impl ProxyPoolManager {
157        fn new() -> Self {
158            ProxyPoolManager {
159                proxies: SegQueue::new(),
160                marker: "www.google.com",
161            }
162        }
163
164        fn add_proxy(&self, address: &str, username: Option<&str>, password: Option<&str>) {
165            self.proxies.push((
166                address.into(),
167                username.map(Into::into),
168                password.map(Into::into),
169            ));
170        }
171    }
172
173    impl Manager for ProxyPoolManager {
174        type Connection = Client;
175        type Error = PSNError;
176
177        fn connect(&self) -> ManagerFuture<'_, Result<Self::Connection, Self::Error>> {
178            Box::pin(async move {
179                let (address, username, password) =
180                    self.proxies.pop().map_err(|_| PSNError::NoClient)?;
181                let proxy = match username {
182                    Some(username) => Proxy::all(&address)
183                        .map(|p| p.basic_auth(&username, password.as_deref().unwrap_or(""))),
184                    None => Proxy::all(&address),
185                };
186
187                Client::builder()
188                    .proxy(proxy.map_err(|_| PSNError::NoClient)?)
189                    .build()
190                    .map_err(|_| PSNError::NoClient)
191            })
192        }
193
194        fn is_valid<'a>(
195            &'a self,
196            conn: &'a mut Self::Connection,
197        ) -> ManagerFuture<'a, Result<(), Self::Error>> {
198            Box::pin(async move {
199                let _ = conn.get(self.marker).send().await?;
200                Ok(())
201            })
202        }
203
204        fn is_closed(&self, _conn: &mut Self::Connection) -> bool {
205            false
206        }
207    }
208
209    impl From<Error> for PSNError {
210        fn from(e: Error) -> Self {
211            PSNError::FromReqwest(e)
212        }
213    }
214
215    impl From<tokio::time::Elapsed> for PSNError {
216        fn from(_: tokio::time::Elapsed) -> Self {
217            PSNError::TimeOut
218        }
219    }
220
221    impl PSN {
222        /// A shortcut for building a temporary http client
223        pub fn new_client() -> Result<Client, PSNError> {
224            ClientBuilder::new().build().map_err(|_| PSNError::NoClient)
225        }
226
227        /// Accept multiple PSNInner and  use them concurrently with a pool.
228        pub async fn new(psn_inner: Vec<PSNInner>) -> Self {
229            let mgr = PSNInnerManager::new();
230
231            let size = psn_inner.len() as u8;
232
233            for inner in psn_inner.into_iter() {
234                mgr.add_psn_inner(inner);
235            }
236
237            let inner_pool = Builder::new()
238                .always_check(true)
239                .idle_timeout(None)
240                .max_lifetime(None)
241                .min_idle(size)
242                .max_size(size)
243                .build(mgr)
244                .await
245                .expect("Failed to build PSNInner pool");
246
247            PSN {
248                inner: inner_pool,
249                client: Self::new_client().expect("Failed to build http client"),
250                proxy_pool: None,
251            }
252        }
253
254        /// Add http proxy pool to combat PSN rate limiter. This is not required.
255        ///# Example:
256        ///```no_run
257        ///use psn_api_rs::{psn::PSN, types::PSNInner, traits::PSNRequest};
258        ///
259        ///#[tokio::main]
260        ///async fn main() -> std::io::Result<()> {
261        ///    let mut psn_inner = PSNInner::new();
262        ///
263        ///    psn_inner.add_refresh_token("refresh_token".into())
264        ///             .add_npsso("npsso".into());
265        ///
266        ///    psn_inner = psn_inner
267        ///            .auth()
268        ///            .await
269        ///            .expect("Authentication failed");
270        ///
271        ///    let proxies = vec![
272        ///        // ("address", Some(username), Some(password)),
273        ///        ("http://abc.com", None, None),
274        ///        ("https://test:1234", None, None),
275        ///        ("http://abc.com", Some("user"), Some("pass")),
276        ///    ];
277        ///
278        ///    let psn = PSN::new(vec![psn_inner]).await.init_proxy(proxies).await;
279        ///
280        ///    Ok(())
281        ///}
282        ///```
283        pub async fn init_proxy(
284            mut self,
285            proxies: Vec<(&str, Option<&str>, Option<&str>)>,
286        ) -> Self {
287            let mgr = ProxyPoolManager::new();
288            let size = proxies.len() as u8;
289            for (address, username, password) in proxies.into_iter() {
290                mgr.add_proxy(address, username, password);
291            }
292
293            let pool = Builder::new()
294                .always_check(false)
295                .idle_timeout(None)
296                .max_lifetime(None)
297                .min_idle(size)
298                .max_size(size)
299                .build(mgr)
300                .await
301                .expect("Failed to build proxy pool");
302
303            self.proxy_pool = Some(pool);
304            self
305        }
306
307        /// Add new proxy into `ProxyPoolManager` on the fly.
308        /// The max proxy pool size is determined by the first proxies vector's length passed to 'PSN::init_proxy'(upper limit pool size is u8).
309        /// Once you hit the max pool size all additional proxies become backup and can only be activated when an active proxy is dropped(connection broken for example)
310        pub fn add_proxy(&self, proxies: Vec<(&str, Option<&str>, Option<&str>)>) {
311            if let Some(pool) = &self.proxy_pool {
312                for (address, username, password) in proxies.into_iter() {
313                    pool.get_manager().add_proxy(address, username, password);
314                }
315            }
316        }
317
318        pub async fn get_profile<T: DeserializeOwned + 'static>(
319            &self,
320            online_id: &str,
321        ) -> Result<T, PSNError> {
322            let (client, psn_inner) = self.get().await?;
323
324            psn_inner.get_profile(&client, online_id).await
325        }
326
327        pub async fn get_titles<T: DeserializeOwned + 'static>(
328            &self,
329            online_id: &str,
330            offset: u32,
331        ) -> Result<T, PSNError> {
332            let (client, psn_inner) = self.get().await?;
333
334            psn_inner.get_titles(&client, online_id, offset).await
335        }
336
337        pub async fn get_trophy_set<T: DeserializeOwned + 'static>(
338            &self,
339            online_id: &str,
340            np_communication_id: &str,
341        ) -> Result<T, PSNError> {
342            let (client, psn_inner) = self.get().await?;
343
344            psn_inner
345                .get_trophy_set(&client, online_id, np_communication_id)
346                .await
347        }
348
349        pub async fn get_message_threads<T: DeserializeOwned + 'static>(
350            &self,
351            offset: u32,
352        ) -> Result<T, PSNError> {
353            let (client, psn_inner) = self.get().await?;
354
355            psn_inner.get_message_threads(&client, offset).await
356        }
357
358        pub async fn get_message_thread<T: DeserializeOwned + 'static>(
359            &self,
360            thread_id: &str,
361        ) -> Result<T, PSNError> {
362            let (client, psn_inner) = self.get().await?;
363
364            psn_inner.get_message_thread(&client, thread_id).await
365        }
366
367        pub async fn generate_message_thread(&self, online_id: &str) -> Result<(), PSNError> {
368            let (client, psn_inner) = self.get().await?;
369
370            psn_inner.generate_message_thread(&client, online_id).await
371        }
372
373        pub async fn leave_message_thread(&self, thread_id: &str) -> Result<(), PSNError> {
374            let (client, psn_inner) = self.get().await?;
375
376            psn_inner.leave_message_thread(&client, thread_id).await
377        }
378
379        pub async fn send_message(
380            &self,
381            online_id: &str,
382            msg: Option<&str>,
383            path: Option<&str>,
384            thread_id: &str,
385        ) -> Result<(), PSNError> {
386            let (client, psn_inner) = self.get().await?;
387
388            psn_inner
389                .send_message(&client, online_id, msg, path, thread_id)
390                .await
391        }
392
393        pub async fn search_store_items<T: DeserializeOwned + 'static>(
394            &self,
395            lang: &str,
396            region: &str,
397            age: &str,
398            name: &str,
399        ) -> Result<T, PSNError> {
400            let (client, psn_inner) = self.get().await?;
401
402            psn_inner
403                .search_store_items(&client, lang, region, age, name)
404                .await
405        }
406
407        async fn get(&self) -> Result<(Client, PoolRef<'_, PSNInnerManager>), PSNError> {
408            let proxy_ref = self.get_proxy_cli().await?;
409            let inner_ref = self.inner.get().await?;
410
411            let client = match proxy_ref.as_ref() {
412                Some(proxy_ref) => (&**proxy_ref).clone(),
413                None => (&self.client).clone(),
414            };
415
416            drop(proxy_ref);
417
418            Ok((client, inner_ref))
419        }
420
421        async fn get_proxy_cli(&self) -> Result<Option<PoolRef<'_, ProxyPoolManager>>, PSNError> {
422            let fut = match self.proxy_pool.as_ref() {
423                Some(pool) => pool.get(),
424                None => return Ok(None),
425            };
426            let pool_ref = fut.await?;
427            Ok(Some(pool_ref))
428        }
429
430        pub fn clients_state(&self) -> u8 {
431            self.proxy_pool
432                .as_ref()
433                .map(|pool| pool.state().idle_connections)
434                .unwrap_or(0)
435        }
436    }
437}