1use reqwest::{ header::{HeaderMap, HeaderValue, AUTHORIZATION, CONTENT_TYPE, USER_AGENT}, 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 Self::new_with_prefix(token, base_url, &format!("Bot {}", token))
38 }
39
40 pub fn new_user(token: &str, base_url: String) -> Self {
42 Self::new_with_prefix(token, base_url, token)
43 }
44
45 fn new_with_prefix(token: &str, base_url: String, auth_value: &str) -> Self {
46 let mut headers = HeaderMap::new();
47 headers.insert(AUTHORIZATION, HeaderValue::from_str(auth_value).unwrap());
48 headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
49 headers.insert(USER_AGENT, HeaderValue::from_static("fluxer-rust"));
50
51 Self {
52 client: reqwest::Client::builder()
53 .default_headers(headers)
54 .build()
55 .unwrap(),
56 base_url,
57 token: token.to_string(),
58 }
59 }
60
61 pub fn get_token(&self) -> &str {
62 &self.token
63 }
64
65 async fn request_json<T: DeserializeOwned>(
66 &self,
67 req: reqwest::RequestBuilder,
68 ) -> Result<T, ClientError> {
69 let resp = req.send().await.map_err(ClientError::Http)?;
70 let status = resp.status();
71 if status == StatusCode::NO_CONTENT {
72 return Err(ClientError::Api("Expected body but got 204".into()));
73 }
74 if !status.is_success() {
75 let text = resp.text().await.unwrap_or_default();
76 return Err(ClientError::Api(format!("HTTP {}: {}", status, text)));
77 }
78 resp.json::<T>().await.map_err(ClientError::Http)
79 }
80
81 async fn request_empty(&self, req: reqwest::RequestBuilder) -> Result<(), ClientError> {
82 let resp = req.send().await.map_err(ClientError::Http)?;
83 let status = resp.status();
84 if !status.is_success() {
85 let text = resp.text().await.unwrap_or_default();
86 return Err(ClientError::Api(format!("HTTP {}: {}", status, text)));
87 }
88 Ok(())
89 }
90
91 pub async fn get_gateway(&self) -> Result<String, ClientError> {
93 let url = format!("{}/gateway/bot", self.base_url);
94 let res = self
95 .request_json::<GatewayBotResponse>(self.client.get(&url))
96 .await?;
97 Ok(res.url)
98 }
99
100 pub async fn get_me(&self) -> Result<User, ClientError> {
102 let url = format!("{}/users/@me", self.base_url);
103 self.request_json(self.client.get(&url)).await
104 }
105
106 pub async fn get_user(&self, user_id: &str) -> Result<User, ClientError> {
108 let url = format!("{}/users/{}", self.base_url, user_id);
109 self.request_json(self.client.get(&url)).await
110 }
111
112 pub async fn get_current_user_guilds(&self) -> Result<Vec<Guild>, ClientError> {
114 let url = format!("{}/users/@me/guilds", self.base_url);
115 self.request_json(self.client.get(&url)).await
116 }
117
118 pub async fn get_channel(&self, channel_id: &str) -> Result<Channel, ClientError> {
120 let url = format!("{}/channels/{}", self.base_url, channel_id);
121 self.request_json(self.client.get(&url)).await
122 }
123
124 pub async fn edit_channel(
126 &self,
127 channel_id: &str,
128 payload: &ChannelCreatePayload,
129 ) -> Result<Channel, ClientError> {
130 let url = format!("{}/channels/{}", self.base_url, channel_id);
131 self.request_json(self.client.patch(&url).json(payload)).await
132 }
133
134 pub async fn delete_channel(&self, channel_id: &str) -> Result<(), ClientError> {
136 let url = format!("{}/channels/{}", self.base_url, channel_id);
137 self.request_empty(self.client.delete(&url)).await
138 }
139
140 pub async fn trigger_typing(&self, channel_id: &str) -> Result<(), ClientError> {
143 let url = format!("{}/channels/{}/typing", self.base_url, channel_id);
144 self.request_empty(self.client.post(&url).body("{}")).await
145 }
146
147 pub async fn get_messages(
161 &self,
162 channel_id: &str,
163 query: GetMessagesQuery,
164 ) -> Result<Vec<Message>, ClientError> {
165 let url = format!(
166 "{}/channels/{}/messages{}",
167 self.base_url,
168 channel_id,
169 query.to_query_string()
170 );
171 self.request_json(self.client.get(&url)).await
172 }
173
174 pub async fn get_message(
176 &self,
177 channel_id: &str,
178 message_id: &str,
179 ) -> Result<Message, ClientError> {
180 let url = format!(
181 "{}/channels/{}/messages/{}",
182 self.base_url, channel_id, message_id
183 );
184 self.request_json(self.client.get(&url)).await
185 }
186
187 pub async fn send_message(
190 &self,
191 channel_id: &str,
192 content: &str,
193 ) -> Result<Message, ClientError> {
194 let url = format!("{}/channels/{}/messages", self.base_url, channel_id);
195 let body = json!({ "content": content });
196 self.request_json(self.client.post(&url).json(&body)).await
197 }
198
199 pub async fn send_message_advanced(
201 &self,
202 channel_id: &str,
203 payload: &MessageCreatePayload,
204 ) -> Result<Message, ClientError> {
205 let url = format!("{}/channels/{}/messages", self.base_url, channel_id);
206 self.request_json(self.client.post(&url).json(payload)).await
207 }
208
209 pub async fn send_embed(
211 &self,
212 channel_id: &str,
213 content: Option<&str>,
214 embeds: Vec<Embed>,
215 ) -> Result<Message, ClientError> {
216 let payload = MessageCreatePayload {
217 content: content.map(|s| s.to_string()),
218 embeds: Some(embeds),
219 ..Default::default()
220 };
221 self.send_message_advanced(channel_id, &payload).await
222 }
223
224 pub async fn reply_to_message(
227 &self,
228 channel_id: &str,
229 message_id: &str,
230 content: &str,
231 ) -> Result<Message, ClientError> {
232 let payload = MessageCreatePayload {
233 content: Some(content.to_string()),
234 message_reference: Some(MessageReference {
235 message_id: message_id.to_string(),
236 channel_id: None,
237 guild_id: None,
238 fail_if_not_exists: Some(true),
239 }),
240 ..Default::default()
241 };
242 self.send_message_advanced(channel_id, &payload).await
243 }
244
245 pub async fn edit_message(
247 &self,
248 channel_id: &str,
249 message_id: &str,
250 content: &str,
251 ) -> Result<Message, ClientError> {
252 let url = format!(
253 "{}/channels/{}/messages/{}",
254 self.base_url, channel_id, message_id
255 );
256 let body = json!({ "content": content });
257 self.request_json(self.client.patch(&url).json(&body)).await
258 }
259
260 pub async fn edit_message_advanced(
262 &self,
263 channel_id: &str,
264 message_id: &str,
265 payload: &MessageCreatePayload,
266 ) -> Result<Message, ClientError> {
267 let url = format!(
268 "{}/channels/{}/messages/{}",
269 self.base_url, channel_id, message_id
270 );
271 self.request_json(self.client.patch(&url).json(payload)).await
272 }
273
274 pub async fn delete_message(
276 &self,
277 channel_id: &str,
278 message_id: &str,
279 ) -> Result<(), ClientError> {
280 let url = format!(
281 "{}/channels/{}/messages/{}",
282 self.base_url, channel_id, message_id
283 );
284 self.request_empty(self.client.delete(&url)).await
285 }
286
287 pub async fn bulk_delete_messages(
289 &self,
290 channel_id: &str,
291 message_ids: Vec<&str>,
292 ) -> Result<(), ClientError> {
293 let url = format!(
294 "{}/channels/{}/messages/bulk-delete",
295 self.base_url, channel_id
296 );
297 let body = json!({ "message_ids": message_ids });
298 self.request_empty(self.client.post(&url).json(&body)).await
299 }
300
301 pub async fn add_reaction(
305 &self,
306 channel_id: &str,
307 message_id: &str,
308 emoji: &str,
309 ) -> Result<(), ClientError> {
310 let encoded = urlencoded(emoji);
311 let url = format!(
312 "{}/channels/{}/messages/{}/reactions/{}/@me",
313 self.base_url, channel_id, message_id, encoded
314 );
315 self.request_empty(self.client.put(&url).body("")).await
316 }
317
318 pub async fn remove_own_reaction(
320 &self,
321 channel_id: &str,
322 message_id: &str,
323 emoji: &str,
324 ) -> Result<(), ClientError> {
325 let encoded = urlencoded(emoji);
326 let url = format!(
327 "{}/channels/{}/messages/{}/reactions/{}/@me",
328 self.base_url, channel_id, message_id, encoded
329 );
330 self.request_empty(self.client.delete(&url)).await
331 }
332
333 pub async fn remove_user_reaction(
335 &self,
336 channel_id: &str,
337 message_id: &str,
338 emoji: &str,
339 user_id: &str,
340 ) -> Result<(), ClientError> {
341 let encoded = urlencoded(emoji);
342 let url = format!(
343 "{}/channels/{}/messages/{}/reactions/{}/{}",
344 self.base_url, channel_id, message_id, encoded, user_id
345 );
346 self.request_empty(self.client.delete(&url)).await
347 }
348
349 pub async fn get_reactions(
351 &self,
352 channel_id: &str,
353 message_id: &str,
354 emoji: &str,
355 ) -> Result<Vec<User>, ClientError> {
356 let encoded = urlencoded(emoji);
357 let url = format!(
358 "{}/channels/{}/messages/{}/reactions/{}",
359 self.base_url, channel_id, message_id, encoded
360 );
361 self.request_json(self.client.get(&url)).await
362 }
363
364 pub async fn clear_reactions(
366 &self,
367 channel_id: &str,
368 message_id: &str,
369 ) -> Result<(), ClientError> {
370 let url = format!(
371 "{}/channels/{}/messages/{}/reactions",
372 self.base_url, channel_id, message_id
373 );
374 self.request_empty(self.client.delete(&url)).await
375 }
376
377 pub async fn clear_reactions_for_emoji(
379 &self,
380 channel_id: &str,
381 message_id: &str,
382 emoji: &str,
383 ) -> Result<(), ClientError> {
384 let encoded = urlencoded(emoji);
385 let url = format!(
386 "{}/channels/{}/messages/{}/reactions/{}",
387 self.base_url, channel_id, message_id, encoded
388 );
389 self.request_empty(self.client.delete(&url)).await
390 }
391
392 pub async fn get_pins(&self, channel_id: &str) -> Result<PinsResponse, ClientError> {
394 let url = format!("{}/channels/{}/messages/pins", self.base_url, channel_id);
395 self.request_json(self.client.get(&url)).await
396 }
397
398 pub async fn pin_message(
400 &self,
401 channel_id: &str,
402 message_id: &str,
403 ) -> Result<(), ClientError> {
404 let url = format!(
405 "{}/channels/{}/pins/{}",
406 self.base_url, channel_id, message_id
407 );
408 self.request_empty(self.client.put(&url).body("")).await
409 }
410
411 pub async fn unpin_message(
413 &self,
414 channel_id: &str,
415 message_id: &str,
416 ) -> Result<(), ClientError> {
417 let url = format!(
418 "{}/channels/{}/pins/{}",
419 self.base_url, channel_id, message_id
420 );
421 self.request_empty(self.client.delete(&url)).await
422 }
423
424 pub async fn get_invite(&self, invite_code: &str) -> Result<Invite, ClientError> {
426 let url = format!(
427 "{}/invites/{}?with_counts=true",
428 self.base_url, invite_code
429 );
430 self.request_json(self.client.get(&url)).await
431 }
432
433 pub async fn create_invite(
435 &self,
436 channel_id: &str,
437 payload: &CreateInvitePayload,
438 ) -> Result<Invite, ClientError> {
439 let url = format!("{}/channels/{}/invites", self.base_url, channel_id);
440 self.request_json(self.client.post(&url).json(payload)).await
441 }
442
443 pub async fn delete_invite(&self, invite_code: &str) -> Result<(), ClientError> {
445 let url = format!("{}/invites/{}", self.base_url, invite_code);
446 self.request_empty(self.client.delete(&url)).await
447 }
448
449 pub async fn get_channel_invites(&self, channel_id: &str) -> Result<Vec<Invite>, ClientError> {
451 let url = format!("{}/channels/{}/invites", self.base_url, channel_id);
452 self.request_json(self.client.get(&url)).await
453 }
454
455 pub async fn get_guild_invites(&self, guild_id: &str) -> Result<Vec<Invite>, ClientError> {
457 let url = format!("{}/guilds/{}/invites", self.base_url, guild_id);
458 self.request_json(self.client.get(&url)).await
459 }
460
461 pub async fn get_guild(&self, guild_id: &str) -> Result<Guild, ClientError> {
463 let url = format!("{}/guilds/{}", self.base_url, guild_id);
464 self.request_json(self.client.get(&url)).await
465 }
466
467 pub async fn edit_guild(
469 &self,
470 guild_id: &str,
471 payload: &EditGuildPayload,
472 ) -> Result<Guild, ClientError> {
473 let url = format!("{}/guilds/{}", self.base_url, guild_id);
474 self.request_json(self.client.patch(&url).json(payload)).await
475 }
476
477 pub async fn delete_guild(&self, guild_id: &str) -> Result<(), ClientError> {
479 let url = format!("{}/guilds/{}", self.base_url, guild_id);
480 self.request_empty(self.client.delete(&url)).await
481 }
482
483 pub async fn get_guild_channels(&self, guild_id: &str) -> Result<Vec<Channel>, ClientError> {
485 let url = format!("{}/guilds/{}/channels", self.base_url, guild_id);
486 self.request_json(self.client.get(&url)).await
487 }
488
489 pub async fn create_channel(
491 &self,
492 guild_id: &str,
493 payload: &ChannelCreatePayload,
494 ) -> Result<Channel, ClientError> {
495 let url = format!("{}/guilds/{}/channels", self.base_url, guild_id);
496 self.request_json(self.client.post(&url).json(payload)).await
497 }
498
499 pub async fn get_guild_member(
501 &self,
502 guild_id: &str,
503 user_id: &str,
504 ) -> Result<Member, ClientError> {
505 let url = format!("{}/guilds/{}/members/{}", self.base_url, guild_id, user_id);
506 self.request_json(self.client.get(&url)).await
507 }
508
509 pub async fn get_guild_members(
511 &self,
512 guild_id: &str,
513 limit: Option<u16>,
514 after: Option<&str>,
515 ) -> Result<Vec<Member>, ClientError> {
516 let mut url = format!("{}/guilds/{}/members?", self.base_url, guild_id);
517 if let Some(l) = limit {
518 url.push_str(&format!("limit={}&", l.min(1000)));
519 }
520 if let Some(a) = after {
521 url.push_str(&format!("after={}", a));
522 }
523 self.request_json(self.client.get(&url)).await
524 }
525
526 pub async fn kick_member(
528 &self,
529 guild_id: &str,
530 user_id: &str,
531 ) -> Result<(), ClientError> {
532 let url = format!("{}/guilds/{}/members/{}", self.base_url, guild_id, user_id);
533 self.request_empty(self.client.delete(&url)).await
534 }
535
536 pub async fn edit_member(
539 &self,
540 guild_id: &str,
541 user_id: &str,
542 payload: &EditMemberPayload,
543 ) -> Result<Member, ClientError> {
544 let url = format!("{}/guilds/{}/members/{}", self.base_url, guild_id, user_id);
545 self.request_json(self.client.patch(&url).json(payload)).await
546 }
547
548 pub async fn add_member_role(
550 &self,
551 guild_id: &str,
552 user_id: &str,
553 role_id: &str,
554 ) -> Result<(), ClientError> {
555 let url = format!(
556 "{}/guilds/{}/members/{}/roles/{}",
557 self.base_url, guild_id, user_id, role_id
558 );
559 self.request_empty(self.client.put(&url).body("")).await
560 }
561
562 pub async fn remove_member_role(
564 &self,
565 guild_id: &str,
566 user_id: &str,
567 role_id: &str,
568 ) -> Result<(), ClientError> {
569 let url = format!(
570 "{}/guilds/{}/members/{}/roles/{}",
571 self.base_url, guild_id, user_id, role_id
572 );
573 self.request_empty(self.client.delete(&url)).await
574 }
575
576 pub async fn ban_member(
578 &self,
579 guild_id: &str,
580 user_id: &str,
581 reason: &str,
582 ) -> Result<(), ClientError> {
583 let url = format!("{}/guilds/{}/bans/{}", self.base_url, guild_id, user_id);
584 let body = json!({ "reason": reason });
585 self.request_empty(self.client.put(&url).json(&body)).await
586 }
587
588 pub async fn unban_member(
590 &self,
591 guild_id: &str,
592 user_id: &str,
593 ) -> Result<(), ClientError> {
594 let url = format!("{}/guilds/{}/bans/{}", self.base_url, guild_id, user_id);
595 self.request_empty(self.client.delete(&url)).await
596 }
597
598 pub async fn get_guild_bans(&self, guild_id: &str) -> Result<Vec<serde_json::Value>, ClientError> {
600 let url = format!("{}/guilds/{}/bans", self.base_url, guild_id);
601 self.request_json(self.client.get(&url)).await
602 }
603
604 pub async fn get_guild_roles(&self, guild_id: &str) -> Result<Vec<Role>, ClientError> {
606 let url = format!("{}/guilds/{}/roles", self.base_url, guild_id);
607 self.request_json(self.client.get(&url)).await
608 }
609
610 pub async fn create_role(
612 &self,
613 guild_id: &str,
614 payload: &CreateRolePayload,
615 ) -> Result<Role, ClientError> {
616 let url = format!("{}/guilds/{}/roles", self.base_url, guild_id);
617 self.request_json(self.client.post(&url).json(payload)).await
618 }
619
620 pub async fn edit_role(
622 &self,
623 guild_id: &str,
624 role_id: &str,
625 payload: &EditRolePayload,
626 ) -> Result<Role, ClientError> {
627 let url = format!("{}/guilds/{}/roles/{}", self.base_url, guild_id, role_id);
628 self.request_json(self.client.patch(&url).json(payload)).await
629 }
630
631 pub async fn delete_role(
633 &self,
634 guild_id: &str,
635 role_id: &str,
636 ) -> Result<(), ClientError> {
637 let url = format!("{}/guilds/{}/roles/{}", self.base_url, guild_id, role_id);
638 self.request_empty(self.client.delete(&url)).await
639 }
640
641 pub async fn get_guild_emojis(&self, guild_id: &str) -> Result<Vec<Emoji>, ClientError> {
643 let url = format!("{}/guilds/{}/emojis", self.base_url, guild_id);
644 self.request_json(self.client.get(&url)).await
645 }
646
647 pub async fn delete_guild_emoji(
649 &self,
650 guild_id: &str,
651 emoji_id: &str,
652 ) -> Result<(), ClientError> {
653 let url = format!("{}/guilds/{}/emojis/{}", self.base_url, guild_id, emoji_id);
654 self.request_empty(self.client.delete(&url)).await
655 }
656
657 pub async fn get_channel_webhooks(
659 &self,
660 channel_id: &str,
661 ) -> Result<Vec<Webhook>, ClientError> {
662 let url = format!("{}/channels/{}/webhooks", self.base_url, channel_id);
663 self.request_json(self.client.get(&url)).await
664 }
665
666 pub async fn get_guild_webhooks(&self, guild_id: &str) -> Result<Vec<Webhook>, ClientError> {
668 let url = format!("{}/guilds/{}/webhooks", self.base_url, guild_id);
669 self.request_json(self.client.get(&url)).await
670 }
671
672 pub async fn create_webhook(
674 &self,
675 channel_id: &str,
676 name: &str,
677 avatar: Option<&str>,
678 ) -> Result<Webhook, ClientError> {
679 let url = format!("{}/channels/{}/webhooks", self.base_url, channel_id);
680 let mut body = json!({ "name": name });
681 if let Some(av) = avatar {
682 body["avatar"] = serde_json::Value::String(av.to_string());
683 }
684 self.request_json(self.client.post(&url).json(&body)).await
685 }
686
687 pub async fn delete_webhook(&self, webhook_id: &str) -> Result<(), ClientError> {
689 let url = format!("{}/webhooks/{}", self.base_url, webhook_id);
690 self.request_empty(self.client.delete(&url)).await
691 }
692
693 pub async fn send_files(
696 &self,
697 channel_id: &str,
698 files: Vec<AttachmentFile>,
699 content: Option<&str>,
700 ) -> Result<Message, ClientError> {
701 let payload = MessageCreatePayload {
702 content: content.map(|s| s.to_string()),
703 ..Default::default()
704 };
705 self.send_message_with_files(channel_id, &payload, files).await
706 }
707
708 pub async fn send_message_with_files(
710 &self,
711 channel_id: &str,
712 payload: &MessageCreatePayload,
713 files: Vec<AttachmentFile>,
714 ) -> Result<Message, ClientError> {
715 use reqwest::multipart::{Form, Part};
716
717 let url = format!("{}/channels/{}/messages", self.base_url, channel_id);
718
719 let mut payload = payload.clone();
720 payload.attachments = Some(
721 files
722 .iter()
723 .enumerate()
724 .map(|(i, f)| AttachmentMetadata {
725 id: i as u64,
726 filename: f.filename.clone(),
727 description: None,
728 })
729 .collect(),
730 );
731 let json_string = serde_json::to_string(&payload)?;
732
733 let mut form = Form::new().text("payload_json", json_string);
734 for (i, file) in files.into_iter().enumerate() {
735 let content_type = file
736 .content_type
737 .unwrap_or_else(|| "application/octet-stream".to_string());
738 let part = Part::bytes(file.data)
739 .file_name(file.filename)
740 .mime_str(&content_type)
741 .map_err(ClientError::Http)?;
742 form = form.part(format!("files[{}]", i), part);
743 }
744
745 let req = self.client.post(&url).multipart(form);
746 self.request_json(req).await
747 }
748
749 pub async fn search_messages(
752 &self,
753 query: &SearchMessagesQuery,
754 ) -> Result<SearchMessagesResponse, ClientError> {
755 let url = format!("{}/search/messages", self.base_url);
756 self.request_json(self.client.post(&url).json(query)).await
757 }
758
759 pub async fn ack_bulk(&self, states: Vec<ReadStateAck>) -> Result<(), ClientError> {
762 let url = format!("{}/read-states/ack-bulk", self.base_url);
763 let body = json!({ "read_states": states });
764 self.request_empty(self.client.post(&url).json(&body)).await
765 }
766
767 pub async fn execute_webhook(
770 &self,
771 webhook_id: &str,
772 webhook_token: &str,
773 payload: &WebhookExecutePayload,
774 ) -> Result<Option<Message>, ClientError> {
775 let url = format!(
776 "{}/webhooks/{}/{}?wait=true",
777 self.base_url, webhook_id, webhook_token
778 );
779 self.request_json(self.client.post(&url).json(payload)).await
780 }
781
782 pub async fn get_webhook_message(
784 &self,
785 webhook_id: &str,
786 webhook_token: &str,
787 message_id: &str,
788 ) -> Result<Message, ClientError> {
789 let url = format!(
790 "{}/webhooks/{}/{}/messages/{}",
791 self.base_url, webhook_id, webhook_token, message_id
792 );
793 self.request_json(self.client.get(&url)).await
794 }
795
796 pub async fn edit_webhook_message(
798 &self,
799 webhook_id: &str,
800 webhook_token: &str,
801 message_id: &str,
802 payload: &WebhookEditPayload,
803 ) -> Result<Message, ClientError> {
804 let url = format!(
805 "{}/webhooks/{}/{}/messages/{}",
806 self.base_url, webhook_id, webhook_token, message_id
807 );
808 self.request_json(self.client.patch(&url).json(payload)).await
809 }
810
811}
812
813fn urlencoded(s: &str) -> String {
814 s.chars()
815 .flat_map(|c| {
816 let mut buf = [0u8; 4];
817 c.encode_utf8(&mut buf);
818 let bytes = &buf[..c.len_utf8()];
819 bytes
820 .iter()
821 .map(|b| match b {
822 b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9'
823 | b'-' | b'_' | b'.' | b'~' | b':' => {
824 char::from(*b).to_string()
825 }
826 other => format!("%{:02X}", other),
827 })
828 .collect::<Vec<_>>()
829 })
830 .collect()
831}