composure_api/
lib.rs

1use composure_commands::command::{ApplicationCommand, CommandsBuilder};
2use reqwest::{
3    header::{self, AUTHORIZATION},
4    IntoUrl, StatusCode,
5};
6use serde::{de::DeserializeOwned, Serialize};
7
8mod application_commands;
9
10pub use application_commands::*;
11
12pub const DISCORD_API: &str = "https://discord.com/api/v10";
13
14#[derive(Debug)]
15pub enum Error {
16    RequestError(reqwest::Error),
17    HeaderError(header::InvalidHeaderValue),
18    Unauthorized,
19    UnknownResponse(String),
20}
21
22pub type Result<T> = std::result::Result<T, Error>;
23
24pub struct DiscordClient {
25    client: reqwest::blocking::Client,
26    application_id: String,
27}
28
29impl DiscordClient {
30    pub fn new(token: &str, application_id: &str) -> Result<DiscordClient> {
31        let mut headers = header::HeaderMap::new();
32
33        headers.insert(
34            AUTHORIZATION,
35            header::HeaderValue::from_str(format!("Bot {token}").as_str())
36                .map_err(|e| Error::HeaderError(e))?,
37        );
38
39        let client = reqwest::blocking::Client::builder()
40            .default_headers(headers)
41            .build()
42            .map_err(|e| Error::RequestError(e))?;
43
44        Ok(DiscordClient {
45            client,
46            application_id: application_id.to_string(),
47        })
48    }
49
50    fn get<T, U: DeserializeOwned>(&self, url: T) -> Result<U>
51    where
52        T: IntoUrl,
53    {
54        let response = self
55            .client
56            .get(url)
57            .send()
58            .map_err(|e| Error::RequestError(e))?;
59
60        match response.status() {
61            StatusCode::UNAUTHORIZED => Err(Error::Unauthorized),
62            _ => Ok(response.json().map_err(|e| Error::RequestError(e))?),
63        }
64    }
65
66    fn post<T, U, R: DeserializeOwned>(&self, url: T, body: &U) -> Result<R>
67    where
68        T: IntoUrl,
69        U: Serialize,
70    {
71        let response = self
72            .client
73            .post(url)
74            .json(body)
75            .send()
76            .map_err(|e| Error::RequestError(e))?;
77
78        match response.status() {
79            StatusCode::UNAUTHORIZED => Err(Error::Unauthorized),
80            _ => Ok(response.json().map_err(|e| Error::RequestError(e))?),
81        }
82    }
83
84    fn put<T, U, R: DeserializeOwned>(&self, url: T, body: &U) -> Result<R>
85    where
86        T: IntoUrl,
87        U: Serialize,
88    {
89        let response = self
90            .client
91            .put(url)
92            .json(body)
93            .send()
94            .map_err(|e| Error::RequestError(e))?;
95
96        match response.status() {
97            StatusCode::UNAUTHORIZED => Err(Error::Unauthorized),
98            StatusCode::OK | StatusCode::CREATED => {
99                Ok(response.json().map_err(|e| Error::RequestError(e))?)
100            }
101            _ => Err(Error::UnknownResponse(
102                response.text().map_err(|e| Error::RequestError(e))?,
103            )),
104        }
105    }
106}
107
108pub trait UpdateCommands {
109    fn update_commands(&self, token: &str) -> Result<Vec<ApplicationCommand>>;
110}
111
112impl UpdateCommands for CommandsBuilder {
113    fn update_commands(&self, token: &str) -> Result<Vec<ApplicationCommand>> {
114        let client = DiscordClient::new(token, &self.application_id.to_string())?;
115
116        let ref_vec = self.commands.iter().map(|c| c).collect();
117
118        let updated_commands = match &self.guild_id {
119            Some(snowflake) => client.overwrite_guild_commands(&snowflake.to_string(), &ref_vec),
120            None => client.overwrite_global_commands(&ref_vec),
121        }?;
122
123        Ok(updated_commands)
124    }
125}