ksoft/
lib.rs

1pub mod model;
2pub mod prelude;
3
4
5#[cfg(feature = "default")]
6use async_trait::async_trait;
7#[cfg(feature = "default")]
8use reqwest::{Client as HttpClient, RequestBuilder};
9#[cfg(feature = "default")]
10use std::sync::Arc;
11#[cfg(feature = "default")]
12use reqwest::header::HeaderMap;
13#[cfg(feature = "default")]
14use serde::de::DeserializeOwned;
15
16#[cfg(feature = "blocking")]
17pub mod blocking;
18#[cfg(feature = "default")]
19pub mod images;
20#[cfg(feature = "default")]
21pub mod bans;
22#[cfg(feature = "default")]
23pub mod kumo;
24#[cfg(feature = "default")]
25pub mod music;
26#[cfg(feature = "default")]
27use crate::{
28    images::Images,
29    bans::Bans,
30    kumo::Kumo,
31    music::Music,
32    model::bans::BanUpdate
33};
34#[cfg(feature = "serenity")]
35use typemap_rev::TypeMapKey;
36
37use std::{
38    error::Error,
39    fmt::{
40        Display, Formatter, Result as FmtResult
41    }
42};
43
44//Asynchronous client
45#[cfg(feature = "default")]
46pub struct Client {
47    pub token: String,
48    pub images: Images,
49    pub bans: Bans,
50    pub kumo: Kumo,
51    pub music: Music,
52    pub http: Arc<HttpClient>
53}
54
55#[cfg(feature = "default")]
56impl Client {
57    pub fn new(token: impl ToString) -> Self {
58        let mut default_auth_header =  HeaderMap::new();
59        default_auth_header.insert("Authorization", format!("Bearer {}", token.to_string()).parse().expect("Cannot parse default headers"));
60        let http_client = Arc::new(HttpClient::builder()
61            .default_headers(default_auth_header)
62            .user_agent("KSoft.rs")
63            .build()
64            .expect("Something went wrong when creating http client"));
65
66        Self {
67            token: token.to_string(),
68            images: Images::new(Arc::clone(&http_client)),
69            bans: Bans::new(Arc::clone(&http_client)),
70            kumo: Kumo::new(Arc::clone(&http_client)),
71            music: Music::new(Arc::clone(&http_client)),
72            http: http_client
73        }
74    }
75
76    /// Sets the event handler
77    ///
78    /// # Example
79    ///
80    /// ```rust,ignore
81    /// use ksoft::{Client, EventHandler};
82    /// use ksoft::model::bans::BanUpdate;
83    /// use ksoft::prelude::async_trait;
84    ///
85    /// #[tokio::main]
86    /// async fn main() {
87    ///     let client = Client::new(std::env::var("KSOFT_TOKEN").expect("KSoft token not found"));
88    ///     client.event_handler(Handler);
89    /// }
90    ///
91    /// struct Handler;
92    ///
93    /// #[async_trait]
94    /// impl EventHandler for Handler {
95    ///     async fn ban_updated(&self, data: Vec<BanUpdate>) {
96    ///         println!("Ban update received: {:#?}", data);
97    ///     }
98    /// }
99    pub fn event_handler(&self, handler: impl EventHandler + Send + Sync + 'static ) {
100        self.bans.event_handler(handler);
101    }
102}
103
104#[cfg(feature = "serenity")]
105impl TypeMapKey for Client {
106    type Value = Arc<Self>;
107}
108
109#[cfg(feature = "default")]
110pub(crate) async fn make_request<S: DeserializeOwned, E: DeserializeOwned>(c: RequestBuilder) -> HttpResult<S, E> {
111    let response = c.send().await?;
112
113    return match response.status().as_u16() {
114        c if c == 429u16 => Err(HttpError::RateLimited),
115        c if c >= 500u16 => Err(HttpError::InternalServerError(response.text().await?)),
116        200u16 => {
117            let data = response.json::<S>().await?;
118            Ok(Ok(data))
119        },
120        _ => {
121            let err = response.json::<E>().await?;
122            Ok(Err(err))
123        }
124    }
125}
126
127const BASE_ENDPOINT: &str = "https://api.ksoft.si";
128
129pub(crate) fn endpoint(to: impl AsRef<str>) -> String {
130    format!("{}{}", BASE_ENDPOINT, to.as_ref())
131}
132
133/// KSoft.rs base http response, not all methods return this
134pub type HttpResult<S, E> = Result<ApiResponse<S, E>, HttpError>;
135
136/// Result renaming used to difference between an http error and an API error or unsuccessful response
137pub type ApiResponse<S, E> = Result<S, E>;
138
139#[derive(Debug)]
140pub enum HttpError {
141    RequestFailed(reqwest::Error),
142    InternalServerError(String),
143    RateLimited
144}
145
146impl From<reqwest::Error> for HttpError {
147    fn from(e: reqwest::Error) -> Self {
148        HttpError::RequestFailed(e)
149    }
150}
151
152impl Error for HttpError {}
153
154impl Display for HttpError {
155    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
156        match self {
157            Self::RequestFailed(why) => write!(f, "Request failed: {}", why.to_string()),
158            Self::InternalServerError(why) => write!(f, "Internal server error: {}", why),
159            Self::RateLimited => write!(f, "KSoft server responded with code 429 (Ratelimited)")
160        }
161    }
162}
163
164#[cfg(feature = "default")]
165#[async_trait]
166pub trait EventHandler: Send + Sync + 'static {
167    ///Event triggered every 5 minutes if there is any ban update
168    async fn ban_updated(&self, _data: Vec<BanUpdate>) {}
169}