1use anyhow::Result;
2use http::{HeaderMap, HeaderValue, Method, StatusCode};
3use reqwest::Url;
4use serde_json::Value as JSON;
5
6use crate::error::Error;
7use crate::types::{
8 guild::CachedGuild,
9 user::{BotUser, User},
10};
11
12static BASE_URL: &str = "https://discord.com/";
13
14#[derive(Debug)]
15pub struct ResumeInfo {
16 pub url: String,
17 pub id: String,
18}
19
20#[derive(Default, Debug)]
21pub struct Cache {
22 pub users: Vec<User>,
23 pub guilds: Vec<CachedGuild>,
24}
25
26#[derive(Debug)]
27pub struct Context {
28 pub user: Option<BotUser>,
29 pub resume_info: Option<ResumeInfo>,
30 pub cache: Cache,
31 pub auth: Option<String>,
32 client: reqwest::Client,
33}
34
35impl Default for Context {
36 fn default() -> Self {
37 let mut headers: HeaderMap<HeaderValue> = HeaderMap::with_capacity(8);
38 headers.insert("Referrer", HeaderValue::from_static("https://discord.com"));
39 headers.insert(
40 "Sec-Ch-Ua",
41 HeaderValue::from_static(r#""Not(A:Brand";v="24", "Chromium";v="122""#),
42 );
43 headers.insert("Sec-Ch-Ua-Mobile", HeaderValue::from_static("?0"));
44 headers.insert("Sec-Ch-Ua-Platform", HeaderValue::from_static("\"Linux\""));
45
46 Self {
47 user: None,
48 resume_info: None,
49 cache: Cache::default(),
50 client: reqwest::Client::builder()
51 .default_headers(headers)
52 .user_agent("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36")
53 .build().expect("Couldn't build client"),
54 auth: None,
55 }
56 }
57}
58
59#[derive(Debug)]
60pub struct Response {
61 pub status: StatusCode,
62 pub headers: HeaderMap<HeaderValue>,
63 pub body: JSON,
64}
65
66impl Context {
67 pub fn set_auth(&mut self, token: String) {
68 self.auth = Some(token);
69 }
70
71 pub async fn request(
72 &mut self,
73 method: Method,
74 endpoint: &str,
75 body: Option<JSON>,
76 ) -> Result<Response> {
77 log::debug!(">> {} {}", method, endpoint);
78
79 let builder = self.client.request(
80 method,
81 Url::parse(BASE_URL)?.join(format!("/api/{}", endpoint).as_str())?,
82 );
83
84 let builder = match &self.auth {
85 Some(a) => builder.header("Authorization", HeaderValue::from_str(a.as_str())?),
86 None => builder,
87 };
88
89 let builder = match body {
90 Some(b) => builder
91 .body(serde_json::to_vec(&b)?)
92 .header("Content-Type", HeaderValue::from_str("application/json")?),
93 None => builder,
94 };
95
96 let res = self.client.execute(builder.build()?).await?;
97
98 let status = res.status();
99 let headers = res.headers().clone();
100 let text = res.text().await?;
101 log::debug!("<< {}", status);
102 log::trace!("{headers:?}");
103 log::trace!("{text}");
104
105 match status.as_u16() {
106 429 => {
107 Err(anyhow::anyhow!(Error::Ratelimit(
108 std::time::Duration::from_secs(headers.get("Retry-After").unwrap().to_str().unwrap().parse::<u64>().unwrap() + 1)
109 )))
110 },
111 _ => Ok(Response {
112 status,
113 headers,
114 body: serde_json::from_str(text.as_str()).unwrap_or(JSON::Null),
115 }),
116 }
117 }
118}