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}