1use reqwest::{ header::{HeaderMap, HeaderValue, AUTHORIZATION, CONTENT_TYPE}, StatusCode, };
7use serde::de::DeserializeOwned;
8use serde_json::json;
9use crate::error::ClientError;
10use crate::model::*;
11
12pub struct Http {
28 pub client: reqwest::Client,
29 pub base_url: String,
30 token: String,
31}
32
33impl Http {
34 pub fn new(token: &str, base_url: String) -> Self {
37 let mut headers = HeaderMap::new();
38 let auth_value = format!("Bot {}", token);
39 headers.insert(AUTHORIZATION, HeaderValue::from_str(&auth_value).unwrap());
40 headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
41
42 Self {
43 client: reqwest::Client::builder()
44 .default_headers(headers)
45 .build()
46 .unwrap(),
47 base_url,
48 token: token.to_string(),
49 }
50 }
51
52 pub fn get_token(&self) -> &str {
53 &self.token
54 }
55
56 async fn request_json<T: DeserializeOwned>(
57 &self,
58 req: reqwest::RequestBuilder,
59 ) -> Result<T, ClientError> {
60 let resp = req.send().await.map_err(ClientError::Http)?;
61 let status = resp.status();
62 if status == StatusCode::NO_CONTENT {
63 return Err(ClientError::Api("Expected body but got 204".into()));
64 }
65 if !status.is_success() {
66 let text = resp.text().await.unwrap_or_default();
67 return Err(ClientError::Api(format!("HTTP {}: {}", status, text)));
68 }
69 resp.json::<T>().await.map_err(ClientError::Http)
70 }
71
72 async fn request_empty(&self, req: reqwest::RequestBuilder) -> Result<(), ClientError> {
73 let resp = req.send().await.map_err(ClientError::Http)?;
74 let status = resp.status();
75 if !status.is_success() {
76 let text = resp.text().await.unwrap_or_default();
77 return Err(ClientError::Api(format!("HTTP {}: {}", status, text)));
78 }
79 Ok(())
80 }
81
82 pub async fn get_gateway(&self) -> Result<String, ClientError> {
84 let url = format!("{}/gateway/bot", self.base_url);
85 let res = self
86 .request_json::<GatewayBotResponse>(self.client.get(&url))
87 .await?;
88 Ok(res.url)
89 }
90
91 pub async fn get_me(&self) -> Result<User, ClientError> {
93 let url = format!("{}/users/@me", self.base_url);
94 self.request_json(self.client.get(&url)).await
95 }
96
97 pub async fn get_user(&self, user_id: &str) -> Result<User, ClientError> {
99 let url = format!("{}/users/{}", self.base_url, user_id);
100 self.request_json(self.client.get(&url)).await
101 }
102
103 pub async fn get_current_user_guilds(&self) -> Result<Vec<Guild>, ClientError> {
105 let url = format!("{}/users/@me/guilds", self.base_url);
106 self.request_json(self.client.get(&url)).await
107 }
108
109 pub async fn get_channel(&self, channel_id: &str) -> Result<Channel, ClientError> {
111 let url = format!("{}/channels/{}", self.base_url, channel_id);
112 self.request_json(self.client.get(&url)).await
113 }
114
115 pub async fn edit_channel(
117 &self,
118 channel_id: &str,
119 payload: &ChannelCreatePayload,
120 ) -> Result<Channel, ClientError> {
121 let url = format!("{}/channels/{}", self.base_url, channel_id);
122 self.request_json(self.client.patch(&url).json(payload)).await
123 }
124
125 pub async fn delete_channel(&self, channel_id: &str) -> Result<(), ClientError> {
127 let url = format!("{}/channels/{}", self.base_url, channel_id);
128 self.request_empty(self.client.delete(&url)).await
129 }
130
131 pub async fn trigger_typing(&self, channel_id: &str) -> Result<(), ClientError> {
134 let url = format!("{}/channels/{}/typing", self.base_url, channel_id);
135 self.request_empty(self.client.post(&url).body("{}")).await
136 }
137
138 pub async fn get_messages(
152 &self,
153 channel_id: &str,
154 query: GetMessagesQuery,
155 ) -> Result<Vec<Message>, ClientError> {
156 let url = format!(
157 "{}/channels/{}/messages{}",
158 self.base_url,
159 channel_id,
160 query.to_query_string()
161 );
162 self.request_json(self.client.get(&url)).await
163 }
164
165 pub async fn get_message(
167 &self,
168 channel_id: &str,
169 message_id: &str,
170 ) -> Result<Message, ClientError> {
171 let url = format!(
172 "{}/channels/{}/messages/{}",
173 self.base_url, channel_id, message_id
174 );
175 self.request_json(self.client.get(&url)).await
176 }
177
178 pub async fn send_message(
181 &self,
182 channel_id: &str,
183 content: &str,
184 ) -> Result<Message, ClientError> {
185 let url = format!("{}/channels/{}/messages", self.base_url, channel_id);
186 let body = json!({ "content": content });
187 self.request_json(self.client.post(&url).json(&body)).await
188 }
189
190 pub async fn send_message_advanced(
192 &self,
193 channel_id: &str,
194 payload: &MessageCreatePayload,
195 ) -> Result<Message, ClientError> {
196 let url = format!("{}/channels/{}/messages", self.base_url, channel_id);
197 self.request_json(self.client.post(&url).json(payload)).await
198 }
199
200 pub async fn send_embed(
202 &self,
203 channel_id: &str,
204 content: Option<&str>,
205 embeds: Vec<Embed>,
206 ) -> Result<Message, ClientError> {
207 let payload = MessageCreatePayload {
208 content: content.map(|s| s.to_string()),
209 embeds: Some(embeds),
210 ..Default::default()
211 };
212 self.send_message_advanced(channel_id, &payload).await
213 }
214
215 pub async fn edit_message(
236 &self,
237 channel_id: &str,
238 message_id: &str,
239 content: &str,
240 ) -> Result<Message, ClientError> {
241 let url = format!(
242 "{}/channels/{}/messages/{}",
243 self.base_url, channel_id, message_id
244 );
245 let body = json!({ "content": content });
246 self.request_json(self.client.patch(&url).json(&body)).await
247 }
248
249 pub async fn edit_message_advanced(
251 &self,
252 channel_id: &str,
253 message_id: &str,
254 payload: &MessageCreatePayload,
255 ) -> Result<Message, ClientError> {
256 let url = format!(
257 "{}/channels/{}/messages/{}",
258 self.base_url, channel_id, message_id
259 );
260 self.request_json(self.client.patch(&url).json(payload)).await
261 }
262
263 pub async fn delete_message(
265 &self,
266 channel_id: &str,
267 message_id: &str,
268 ) -> Result<(), ClientError> {
269 let url = format!(
270 "{}/channels/{}/messages/{}",
271 self.base_url, channel_id, message_id
272 );
273 self.request_empty(self.client.delete(&url)).await
274 }
275
276 pub async fn bulk_delete_messages(
278 &self,
279 channel_id: &str,
280 message_ids: Vec<&str>,
281 ) -> Result<(), ClientError> {
282 let url = format!(
283 "{}/channels/{}/messages/bulk-delete",
284 self.base_url, channel_id
285 );
286 let body = json!({ "message_ids": message_ids });
287 self.request_empty(self.client.post(&url).json(&body)).await
288 }
289
290 pub async fn add_reaction(
294 &self,
295 channel_id: &str,
296 message_id: &str,
297 emoji: &str,
298 ) -> Result<(), ClientError> {
299 let encoded = urlencoded(emoji);
300 let url = format!(
301 "{}/channels/{}/messages/{}/reactions/{}/@me",
302 self.base_url, channel_id, message_id, encoded
303 );
304 self.request_empty(self.client.put(&url).body("")).await
305 }
306
307 pub async fn remove_own_reaction(
309 &self,
310 channel_id: &str,
311 message_id: &str,
312 emoji: &str,
313 ) -> Result<(), ClientError> {
314 let encoded = urlencoded(emoji);
315 let url = format!(
316 "{}/channels/{}/messages/{}/reactions/{}/@me",
317 self.base_url, channel_id, message_id, encoded
318 );
319 self.request_empty(self.client.delete(&url)).await
320 }
321
322 pub async fn remove_user_reaction(
324 &self,
325 channel_id: &str,
326 message_id: &str,
327 emoji: &str,
328 user_id: &str,
329 ) -> Result<(), ClientError> {
330 let encoded = urlencoded(emoji);
331 let url = format!(
332 "{}/channels/{}/messages/{}/reactions/{}/{}",
333 self.base_url, channel_id, message_id, encoded, user_id
334 );
335 self.request_empty(self.client.delete(&url)).await
336 }
337
338 pub async fn get_reactions(
340 &self,
341 channel_id: &str,
342 message_id: &str,
343 emoji: &str,
344 ) -> Result<Vec<User>, ClientError> {
345 let encoded = urlencoded(emoji);
346 let url = format!(
347 "{}/channels/{}/messages/{}/reactions/{}",
348 self.base_url, channel_id, message_id, encoded
349 );
350 self.request_json(self.client.get(&url)).await
351 }
352
353 pub async fn clear_reactions(
355 &self,
356 channel_id: &str,
357 message_id: &str,
358 ) -> Result<(), ClientError> {
359 let url = format!(
360 "{}/channels/{}/messages/{}/reactions",
361 self.base_url, channel_id, message_id
362 );
363 self.request_empty(self.client.delete(&url)).await
364 }
365
366 pub async fn clear_reactions_for_emoji(
368 &self,
369 channel_id: &str,
370 message_id: &str,
371 emoji: &str,
372 ) -> Result<(), ClientError> {
373 let encoded = urlencoded(emoji);
374 let url = format!(
375 "{}/channels/{}/messages/{}/reactions/{}",
376 self.base_url, channel_id, message_id, encoded
377 );
378 self.request_empty(self.client.delete(&url)).await
379 }
380
381 pub async fn get_pins(&self, channel_id: &str) -> Result<PinsResponse, ClientError> {
383 let url = format!("{}/channels/{}/messages/pins", self.base_url, channel_id);
384 self.request_json(self.client.get(&url)).await
385 }
386
387 pub async fn pin_message(
389 &self,
390 channel_id: &str,
391 message_id: &str,
392 ) -> Result<(), ClientError> {
393 let url = format!(
394 "{}/channels/{}/pins/{}",
395 self.base_url, channel_id, message_id
396 );
397 self.request_empty(self.client.put(&url).body("")).await
398 }
399
400 pub async fn unpin_message(
402 &self,
403 channel_id: &str,
404 message_id: &str,
405 ) -> Result<(), ClientError> {
406 let url = format!(
407 "{}/channels/{}/pins/{}",
408 self.base_url, channel_id, message_id
409 );
410 self.request_empty(self.client.delete(&url)).await
411 }
412
413 pub async fn get_invite(&self, invite_code: &str) -> Result<Invite, ClientError> {
415 let url = format!(
416 "{}/invites/{}?with_counts=true",
417 self.base_url, invite_code
418 );
419 self.request_json(self.client.get(&url)).await
420 }
421
422 pub async fn create_invite(
424 &self,
425 channel_id: &str,
426 payload: &CreateInvitePayload,
427 ) -> Result<Invite, ClientError> {
428 let url = format!("{}/channels/{}/invites", self.base_url, channel_id);
429 self.request_json(self.client.post(&url).json(payload)).await
430 }
431
432 pub async fn delete_invite(&self, invite_code: &str) -> Result<(), ClientError> {
434 let url = format!("{}/invites/{}", self.base_url, invite_code);
435 self.request_empty(self.client.delete(&url)).await
436 }
437
438 pub async fn get_channel_invites(&self, channel_id: &str) -> Result<Vec<Invite>, ClientError> {
440 let url = format!("{}/channels/{}/invites", self.base_url, channel_id);
441 self.request_json(self.client.get(&url)).await
442 }
443
444 pub async fn get_guild_invites(&self, guild_id: &str) -> Result<Vec<Invite>, ClientError> {
446 let url = format!("{}/guilds/{}/invites", self.base_url, guild_id);
447 self.request_json(self.client.get(&url)).await
448 }
449
450 pub async fn get_guild(&self, guild_id: &str) -> Result<Guild, ClientError> {
452 let url = format!("{}/guilds/{}", self.base_url, guild_id);
453 self.request_json(self.client.get(&url)).await
454 }
455
456 pub async fn edit_guild(
458 &self,
459 guild_id: &str,
460 payload: &EditGuildPayload,
461 ) -> Result<Guild, ClientError> {
462 let url = format!("{}/guilds/{}", self.base_url, guild_id);
463 self.request_json(self.client.patch(&url).json(payload)).await
464 }
465
466 pub async fn delete_guild(&self, guild_id: &str) -> Result<(), ClientError> {
468 let url = format!("{}/guilds/{}", self.base_url, guild_id);
469 self.request_empty(self.client.delete(&url)).await
470 }
471
472 pub async fn get_guild_channels(&self, guild_id: &str) -> Result<Vec<Channel>, ClientError> {
474 let url = format!("{}/guilds/{}/channels", self.base_url, guild_id);
475 self.request_json(self.client.get(&url)).await
476 }
477
478 pub async fn create_channel(
480 &self,
481 guild_id: &str,
482 payload: &ChannelCreatePayload,
483 ) -> Result<Channel, ClientError> {
484 let url = format!("{}/guilds/{}/channels", self.base_url, guild_id);
485 self.request_json(self.client.post(&url).json(payload)).await
486 }
487
488 pub async fn get_guild_member(
490 &self,
491 guild_id: &str,
492 user_id: &str,
493 ) -> Result<Member, ClientError> {
494 let url = format!("{}/guilds/{}/members/{}", self.base_url, guild_id, user_id);
495 self.request_json(self.client.get(&url)).await
496 }
497
498 pub async fn get_guild_members(
500 &self,
501 guild_id: &str,
502 limit: Option<u16>,
503 after: Option<&str>,
504 ) -> Result<Vec<Member>, ClientError> {
505 let mut url = format!("{}/guilds/{}/members?", self.base_url, guild_id);
506 if let Some(l) = limit {
507 url.push_str(&format!("limit={}&", l.min(1000)));
508 }
509 if let Some(a) = after {
510 url.push_str(&format!("after={}", a));
511 }
512 self.request_json(self.client.get(&url)).await
513 }
514
515 pub async fn kick_member(
517 &self,
518 guild_id: &str,
519 user_id: &str,
520 ) -> Result<(), ClientError> {
521 let url = format!("{}/guilds/{}/members/{}", self.base_url, guild_id, user_id);
522 self.request_empty(self.client.delete(&url)).await
523 }
524
525 pub async fn edit_member(
528 &self,
529 guild_id: &str,
530 user_id: &str,
531 payload: &EditMemberPayload,
532 ) -> Result<Member, ClientError> {
533 let url = format!("{}/guilds/{}/members/{}", self.base_url, guild_id, user_id);
534 self.request_json(self.client.patch(&url).json(payload)).await
535 }
536
537 pub async fn ban_member(
549 &self,
550 guild_id: &str,
551 user_id: &str,
552 reason: &str,
553 ) -> Result<(), ClientError> {
554 let url = format!("{}/guilds/{}/bans/{}", self.base_url, guild_id, user_id);
555 let body = json!({ "reason": reason });
556 self.request_empty(self.client.put(&url).json(&body)).await
557 }
558
559 pub async fn unban_member(
561 &self,
562 guild_id: &str,
563 user_id: &str,
564 ) -> Result<(), ClientError> {
565 let url = format!("{}/guilds/{}/bans/{}", self.base_url, guild_id, user_id);
566 self.request_empty(self.client.delete(&url)).await
567 }
568
569 pub async fn get_guild_bans(&self, guild_id: &str) -> Result<Vec<serde_json::Value>, ClientError> {
571 let url = format!("{}/guilds/{}/bans", self.base_url, guild_id);
572 self.request_json(self.client.get(&url)).await
573 }
574
575 pub async fn get_guild_roles(&self, guild_id: &str) -> Result<Vec<Role>, ClientError> {
577 let url = format!("{}/guilds/{}/roles", self.base_url, guild_id);
578 self.request_json(self.client.get(&url)).await
579 }
580
581 pub async fn create_role(
583 &self,
584 guild_id: &str,
585 payload: &CreateRolePayload,
586 ) -> Result<Role, ClientError> {
587 let url = format!("{}/guilds/{}/roles", self.base_url, guild_id);
588 self.request_json(self.client.post(&url).json(payload)).await
589 }
590
591 pub async fn edit_role(
593 &self,
594 guild_id: &str,
595 role_id: &str,
596 payload: &EditRolePayload,
597 ) -> Result<Role, ClientError> {
598 let url = format!("{}/guilds/{}/roles/{}", self.base_url, guild_id, role_id);
599 self.request_json(self.client.patch(&url).json(payload)).await
600 }
601
602 pub async fn delete_role(
604 &self,
605 guild_id: &str,
606 role_id: &str,
607 ) -> Result<(), ClientError> {
608 let url = format!("{}/guilds/{}/roles/{}", self.base_url, guild_id, role_id);
609 self.request_empty(self.client.delete(&url)).await
610 }
611
612 pub async fn get_guild_emojis(&self, guild_id: &str) -> Result<Vec<Emoji>, ClientError> {
614 let url = format!("{}/guilds/{}/emojis", self.base_url, guild_id);
615 self.request_json(self.client.get(&url)).await
616 }
617
618 pub async fn delete_guild_emoji(
620 &self,
621 guild_id: &str,
622 emoji_id: &str,
623 ) -> Result<(), ClientError> {
624 let url = format!("{}/guilds/{}/emojis/{}", self.base_url, guild_id, emoji_id);
625 self.request_empty(self.client.delete(&url)).await
626 }
627
628 pub async fn get_channel_webhooks(
630 &self,
631 channel_id: &str,
632 ) -> Result<Vec<Webhook>, ClientError> {
633 let url = format!("{}/channels/{}/webhooks", self.base_url, channel_id);
634 self.request_json(self.client.get(&url)).await
635 }
636
637 pub async fn get_guild_webhooks(&self, guild_id: &str) -> Result<Vec<Webhook>, ClientError> {
639 let url = format!("{}/guilds/{}/webhooks", self.base_url, guild_id);
640 self.request_json(self.client.get(&url)).await
641 }
642
643 pub async fn create_webhook(
645 &self,
646 channel_id: &str,
647 name: &str,
648 avatar: Option<&str>,
649 ) -> Result<Webhook, ClientError> {
650 let url = format!("{}/channels/{}/webhooks", self.base_url, channel_id);
651 let mut body = json!({ "name": name });
652 if let Some(av) = avatar {
653 body["avatar"] = serde_json::Value::String(av.to_string());
654 }
655 self.request_json(self.client.post(&url).json(&body)).await
656 }
657
658 pub async fn delete_webhook(&self, webhook_id: &str) -> Result<(), ClientError> {
660 let url = format!("{}/webhooks/{}", self.base_url, webhook_id);
661 self.request_empty(self.client.delete(&url)).await
662 }
663
664 pub async fn send_files(
667 &self,
668 channel_id: &str,
669 files: Vec<AttachmentFile>,
670 content: Option<&str>,
671 ) -> Result<Message, ClientError> {
672 let payload = MessageCreatePayload {
673 content: content.map(|s| s.to_string()),
674 ..Default::default()
675 };
676 self.send_message_with_files(channel_id, &payload, files).await
677 }
678
679 pub async fn send_message_with_files(
681 &self,
682 channel_id: &str,
683 payload: &MessageCreatePayload,
684 files: Vec<AttachmentFile>,
685 ) -> Result<Message, ClientError> {
686 use reqwest::multipart::{Form, Part};
687
688 let url = format!("{}/channels/{}/messages", self.base_url, channel_id);
689
690 let mut payload = payload.clone();
691 payload.attachments = Some(
692 files
693 .iter()
694 .enumerate()
695 .map(|(i, f)| AttachmentMetadata {
696 id: i as u64,
697 filename: f.filename.clone(),
698 description: None,
699 })
700 .collect(),
701 );
702 let json_string = serde_json::to_string(&payload)?;
703
704 let mut form = Form::new().text("payload_json", json_string);
705 for (i, file) in files.into_iter().enumerate() {
706 let content_type = file
707 .content_type
708 .unwrap_or_else(|| "application/octet-stream".to_string());
709 let part = Part::bytes(file.data)
710 .file_name(file.filename)
711 .mime_str(&content_type)
712 .map_err(ClientError::Http)?;
713 form = form.part(format!("files[{}]", i), part);
714 }
715
716 let req = self.client.post(&url).multipart(form);
717 self.request_json(req).await
718 }
719
720 pub async fn execute_webhook(
723 &self,
724 webhook_id: &str,
725 webhook_token: &str,
726 payload: &WebhookExecutePayload,
727 ) -> Result<Option<Message>, ClientError> {
728 let url = format!(
729 "{}/webhooks/{}/{}?wait=true",
730 self.base_url, webhook_id, webhook_token
731 );
732 self.request_json(self.client.post(&url).json(payload)).await
733 }
734}
735
736fn urlencoded(s: &str) -> String {
737 s.chars()
738 .flat_map(|c| {
739 let mut buf = [0u8; 4];
740 c.encode_utf8(&mut buf);
741 let bytes = &buf[..c.len_utf8()];
742 bytes
743 .iter()
744 .map(|b| match b {
745 b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9'
746 | b'-' | b'_' | b'.' | b'~' | b':' => {
747 char::from(*b).to_string()
748 }
749 other => format!("%{:02X}", other),
750 })
751 .collect::<Vec<_>>()
752 })
753 .collect()
754}