1use std::result::Result as StdResult;
2
3use reqwest::{
4 header::{HeaderMap, HeaderValue},
5 multipart::{Form, Part},
6 RequestBuilder,
7};
8use robespierre_models::{
9 auth::Session,
10 autumn::{AttachmentId, AttachmentTag},
11 core::RevoltConfiguration,
12 id::UserId,
13};
14
15#[derive(Debug, thiserror::Error)]
17pub enum HttpError {
18 #[error("reqwest: {0}")]
19 Reqwest(#[from] reqwest::Error),
20
21 #[error("decoding: {0}")]
22 Decoding(#[from] serde_json::Error),
23}
24
25pub type Result<T = ()> = StdResult<T, HttpError>;
26
27#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
29pub enum HttpAuthentication<'a> {
30 BotToken { token: &'a str },
31 UserSession { session_token: &'a str },
32}
33trait AuthExt: Sized {
34 fn auth(self, auth: &HttpAuthentication) -> Self;
35}
36
37impl AuthExt for RequestBuilder {
38 fn auth(self, auth: &HttpAuthentication) -> Self {
39 match auth {
40 HttpAuthentication::BotToken { token } => self.header("x-bot-token", *token),
41 HttpAuthentication::UserSession { session_token } => {
42 self.header("x-session-token", *session_token)
43 }
44 }
45 }
46}
47
48impl AuthExt for HeaderMap {
49 fn auth(mut self, auth: &HttpAuthentication) -> Self {
50 match auth {
51 HttpAuthentication::BotToken { token } => {
52 self.insert("x-bot-token", token.parse().unwrap());
53 }
54 HttpAuthentication::UserSession { session_token } => {
55 self.insert("x-session-token", session_token.parse().unwrap());
56 }
57 }
58
59 self
60 }
61}
62
63#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
64pub enum AuthType {
65 Bot,
66 UserSession,
67}
68
69macro_rules! ep {
70 ($self:ident, $ep:literal $($args:tt)*) => {
71 format!(concat!("{}", $ep), $self.api_root, $($args)*)
72 };
73
74 (api_root = $api_root:expr, $ep:literal $($args:tt)*) => {
75 format!(concat!("{}", $ep), $api_root, $($args)*)
76 };
77}
78
79macro_rules! autumn_tag_upload {
80 ($self:expr, $tag:expr) => {
81 format!("{}/{}", $self.revolt_config.features.autumn.url(), $tag)
82 };
83}
84
85impl<'a> From<&'a Session> for HttpAuthentication<'a> {
86 fn from(s: &'a Session) -> Self {
87 HttpAuthentication::UserSession {
88 session_token: &s.token.0,
89 }
90 }
91}
92
93pub struct Http {
95 client: reqwest::Client,
96 api_root: String,
97 revolt_config: RevoltConfiguration,
98
99 auth_type: AuthType,
100}
101
102pub mod core;
103
104pub mod onboarding;
105
106pub mod account;
107
108pub mod session;
109
110pub mod users_information;
111
112pub mod direct_messaging;
113
114pub mod relationships;
115
116pub mod channel_information;
117
118pub mod channel_invites;
119
120pub mod channel_permissions;
121
122pub mod messaging;
123
124pub mod groups;
125
126pub mod voice;
127
128pub mod server_information;
129
130pub mod server_members;
131
132pub mod server_permissions;
133
134pub mod bots;
135
136pub mod invites;
137
138pub mod sync;
139
140pub mod web_push;
141
142mod impl_prelude {
143 pub use super::Http;
144 pub use super::Result;
145}
146
147impl Http {
148 pub async fn new<'auth>(auth: impl Into<HttpAuthentication<'auth>>) -> Result<Self> {
150 Self::new_with_url(auth, "https://api.revolt.chat").await
151 }
152
153 pub async fn new_with_url<'auth>(
157 auth: impl Into<HttpAuthentication<'auth>>,
158 api_root: &str,
159 ) -> Result<Self> {
160 let auth = auth.into();
161 let mut default_headers = HeaderMap::new().auth(&auth);
162 default_headers.insert(reqwest::header::ACCEPT, HeaderValue::from_static("*/*"));
163 let client = reqwest::Client::builder()
164 .default_headers(default_headers)
165 .build()
166 .unwrap();
167 let revolt_config = Self::get_revolt_config(&client, api_root).await?;
168 let auth_type = match auth {
169 HttpAuthentication::BotToken { .. } => AuthType::Bot,
170 HttpAuthentication::UserSession { .. } => AuthType::UserSession,
171 };
172 Ok(Self {
173 client,
174 api_root: api_root.to_string(),
175 revolt_config,
176 auth_type,
177 })
178 }
179
180 fn client_user_session_auth_type(&self) -> &reqwest::Client {
181 match self.auth_type {
182 AuthType::Bot => panic!("Cannot use route when using a bot auth"),
183 AuthType::UserSession => &self.client,
184 }
185 }
186
187 pub fn get_ws_url(&self) -> &str {
189 &self.revolt_config.ws
190 }
191
192 pub async fn get_self_id(&self) -> Result<UserId> {
193 Ok(self.fetch_account().await?.id)
194 }
195
196 pub async fn upload_autumn(
198 &self,
199 tag: AttachmentTag,
200 name: String,
201 bytes: Vec<u8>,
202 ) -> Result<AttachmentId> {
203 #[derive(serde::Deserialize)]
204 struct AutumnUploadResponse {
205 id: AttachmentId,
206 }
207
208 let part = Part::bytes(bytes).file_name(name.clone());
209 let form = Form::new().part(name, part);
210 let req = self
211 .client
212 .post(autumn_tag_upload!(self, tag))
213 .multipart(form);
214 let resp = req
215 .send()
216 .await?
217 .error_for_status()?
218 .json::<AutumnUploadResponse>()
219 .await?;
220 Ok(resp.id)
221 }
222}