1use serde::{Deserialize, Serialize, Serializer};
2use std::collections::HashSet;
3
4type Snowflake = String;
5
6#[derive(Deserialize, Debug)]
7pub struct Webhook {
8 pub id: Snowflake,
9 #[serde(rename = "type")]
10 pub webhook_type: i8,
11 pub guild_id: Snowflake,
12 pub channel_id: Snowflake,
13 pub name: Option<String>,
14 pub avatar: Option<String>,
15 pub token: String,
16 pub application_id: Option<Snowflake>,
17}
18
19#[derive(Debug)]
20pub(crate) struct MessageContext {
21 custom_ids: HashSet<String>,
22}
23
24impl MessageContext {
25 pub fn register_custom_id(&mut self, id: &str) -> bool {
37 self.custom_ids.insert(id.to_string())
38 }
39
40 pub fn new() -> MessageContext {
41 MessageContext {
42 custom_ids: HashSet::new(),
43 }
44 }
45}
46
47#[derive(Serialize, Debug)]
48pub struct Message {
49 pub content: Option<String>,
50 pub username: Option<String>,
51 pub avatar_url: Option<String>,
52 pub tts: bool,
53 pub embeds: Vec<Embed>,
54 pub allow_mentions: Option<AllowedMentions>,
55 #[serde(rename = "components")]
56 pub action_rows: Vec<ActionRow>,
57}
58
59impl Message {
60 pub fn new() -> Self {
61 Self {
62 content: None,
63 username: None,
64 avatar_url: None,
65 tts: false,
66 embeds: vec![],
67 allow_mentions: None,
68 action_rows: vec![],
69 }
70 }
71
72 pub fn content(&mut self, content: &str) -> &mut Self {
73 self.content = Some(content.to_owned());
74 self
75 }
76
77 pub fn username(&mut self, username: &str) -> &mut Self {
78 self.username = Some(username.to_owned());
79 self
80 }
81
82 pub fn avatar_url(&mut self, avatar_url: &str) -> &mut Self {
83 self.avatar_url = Some(avatar_url.to_owned());
84 self
85 }
86
87 pub fn tts(&mut self, tts: bool) -> &mut Self {
88 self.tts = tts;
89 self
90 }
91
92 pub fn embed<Func>(&mut self, func: Func) -> &mut Self
93 where
94 Func: Fn(&mut Embed) -> &mut Embed,
95 {
96 let mut embed = Embed::new();
97 func(&mut embed);
98 self.embeds.push(embed);
99
100 self
101 }
102
103 pub fn action_row<Func>(&mut self, func: Func) -> &mut Self
104 where
105 Func: Fn(&mut ActionRow) -> &mut ActionRow,
106 {
107 let mut row = ActionRow::new();
108 func(&mut row);
109 self.action_rows.push(row);
110
111 self
112 }
113
114 pub fn max_action_row_count() -> usize {
115 5
116 }
117
118 pub fn label_max_len() -> usize {
119 80
120 }
121
122 pub fn custom_id_max_len() -> usize {
123 100
124 }
125
126 pub fn allow_mentions(
127 &mut self,
128 parse: Option<Vec<AllowedMention>>,
129 roles: Option<Vec<Snowflake>>,
130 users: Option<Vec<Snowflake>>,
131 replied_user: bool,
132 ) -> &mut Self {
133 self.allow_mentions = Some(AllowedMentions::new(parse, roles, users, replied_user));
134 self
135 }
136}
137
138#[derive(Serialize, Debug)]
139pub struct Embed {
140 pub title: Option<String>,
141 #[serde(rename = "type")]
142 embed_type: String,
143 pub description: Option<String>,
144 pub url: Option<String>,
145 pub timestamp: Option<String>,
147 pub color: Option<String>,
148 pub footer: Option<EmbedFooter>,
149 pub image: Option<EmbedImage>,
150 pub video: Option<EmbedVideo>,
151 pub thumbnail: Option<EmbedThumbnail>,
152 pub provider: Option<EmbedProvider>,
153 pub author: Option<EmbedAuthor>,
154 pub fields: Vec<EmbedField>,
155}
156
157impl Embed {
158 pub fn new() -> Self {
159 Self {
160 title: None,
161 embed_type: String::from("rich"),
162 description: None,
163 url: None,
164 timestamp: None,
165 color: None,
166 footer: None,
167 image: None,
168 video: None,
169 thumbnail: None,
170 provider: None,
171 author: None,
172 fields: vec![],
173 }
174 }
175
176 pub fn title(&mut self, title: &str) -> &mut Self {
177 self.title = Some(title.to_owned());
178 self
179 }
180
181 pub fn description(&mut self, description: &str) -> &mut Self {
182 self.description = Some(description.to_owned());
183 self
184 }
185
186 pub fn url(&mut self, url: &str) -> &mut Self {
187 self.url = Some(url.to_owned());
188 self
189 }
190
191 pub fn timestamp(&mut self, timestamp: &str) -> &mut Self {
192 self.timestamp = Some(timestamp.to_owned());
193 self
194 }
195
196 pub fn color(&mut self, color: &str) -> &mut Self {
197 self.color = Some(color.to_owned());
198 self
199 }
200
201 pub fn footer(&mut self, text: &str, icon_url: Option<String>) -> &mut Self {
202 self.footer = Some(EmbedFooter::new(text, icon_url));
203 self
204 }
205
206 pub fn image(&mut self, url: &str) -> &mut Self {
207 self.image = Some(EmbedImage::new(url));
208 self
209 }
210
211 pub fn video(&mut self, url: &str) -> &mut Self {
212 self.video = Some(EmbedVideo::new(url));
213 self
214 }
215
216 pub fn thumbnail(&mut self, url: &str) -> &mut Self {
217 self.thumbnail = Some(EmbedThumbnail::new(url));
218 self
219 }
220
221 pub fn provider(&mut self, name: &str, url: &str) -> &mut Self {
222 self.provider = Some(EmbedProvider::new(name, url));
223 self
224 }
225
226 pub fn author(
227 &mut self,
228 name: &str,
229 url: Option<String>,
230 icon_url: Option<String>,
231 ) -> &mut Self {
232 self.author = Some(EmbedAuthor::new(name, url, icon_url));
233 self
234 }
235
236 pub fn field(&mut self, name: &str, value: &str, inline: bool) -> &mut Self {
237 if self.fields.len() == 25 {
238 panic!("You can't have more than 25 fields in an embed!")
239 }
240
241 self.fields.push(EmbedField::new(name, value, inline));
242 self
243 }
244}
245
246#[derive(Serialize, Debug)]
247pub struct EmbedField {
248 pub name: String,
249 pub value: String,
250 pub inline: bool,
251}
252
253impl EmbedField {
254 pub fn new(name: &str, value: &str, inline: bool) -> Self {
255 Self {
256 name: name.to_owned(),
257 value: value.to_owned(),
258 inline,
259 }
260 }
261}
262
263#[derive(Serialize, Debug)]
264pub struct EmbedFooter {
265 pub text: String,
266 pub icon_url: Option<String>,
267}
268
269impl EmbedFooter {
270 pub fn new(text: &str, icon_url: Option<String>) -> Self {
271 Self {
272 text: text.to_owned(),
273 icon_url,
274 }
275 }
276}
277
278pub type EmbedImage = EmbedUrlSource;
279pub type EmbedThumbnail = EmbedUrlSource;
280pub type EmbedVideo = EmbedUrlSource;
281
282#[derive(Serialize, Debug)]
283pub struct EmbedUrlSource {
284 pub url: String,
285}
286
287impl EmbedUrlSource {
288 pub fn new(url: &str) -> Self {
289 Self {
290 url: url.to_owned(),
291 }
292 }
293}
294
295#[derive(Serialize, Debug)]
296pub struct EmbedProvider {
297 pub name: String,
298 pub url: String,
299}
300
301impl EmbedProvider {
302 pub fn new(name: &str, url: &str) -> Self {
303 Self {
304 name: name.to_owned(),
305 url: url.to_owned(),
306 }
307 }
308}
309
310#[derive(Serialize, Debug)]
311pub struct EmbedAuthor {
312 pub name: String,
313 pub url: Option<String>,
314 pub icon_url: Option<String>,
315}
316
317impl EmbedAuthor {
318 pub fn new(name: &str, url: Option<String>, icon_url: Option<String>) -> Self {
319 Self {
320 name: name.to_owned(),
321 url,
322 icon_url,
323 }
324 }
325}
326
327pub enum AllowedMention {
328 RoleMention,
329 UserMention,
330 EveryoneMention,
331}
332
333fn resolve_allowed_mention_name(allowed_mention: AllowedMention) -> String {
334 match allowed_mention {
335 AllowedMention::RoleMention => "roles".to_string(),
336 AllowedMention::UserMention => "users".to_string(),
337 AllowedMention::EveryoneMention => "everyone".to_string(),
338 }
339}
340
341#[derive(Serialize, Debug)]
342pub struct AllowedMentions {
343 pub parse: Option<Vec<String>>,
344 pub roles: Option<Vec<Snowflake>>,
345 pub users: Option<Vec<Snowflake>>,
346 pub replied_user: bool,
347}
348
349impl AllowedMentions {
350 pub fn new(
351 parse: Option<Vec<AllowedMention>>,
352 roles: Option<Vec<Snowflake>>,
353 users: Option<Vec<Snowflake>>,
354 replied_user: bool,
355 ) -> Self {
356 let mut parse_strings: Vec<String> = vec![];
357 if parse.is_some() {
358 parse
359 .unwrap()
360 .into_iter()
361 .for_each(|x| parse_strings.push(resolve_allowed_mention_name(x)))
362 }
363
364 Self {
365 parse: Some(parse_strings),
366 roles,
367 users,
368 replied_user,
369 }
370 }
371}
372
373#[derive(Debug)]
376enum NonCompositeComponent {
377 Button(Button),
378}
379
380impl Serialize for NonCompositeComponent {
381 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
382 where
383 S: Serializer,
384 {
385 match self {
386 NonCompositeComponent::Button(button) => button.serialize(serializer),
387 }
388 }
389}
390
391#[derive(Serialize, Debug)]
392pub struct ActionRow {
393 #[serde(rename = "type")]
394 pub component_type: u8,
395 components: Vec<NonCompositeComponent>,
396}
397
398impl ActionRow {
399 fn new() -> ActionRow {
400 ActionRow {
401 component_type: 1,
402 components: vec![],
403 }
404 }
405
406 pub fn link_button<Func>(&mut self, button_mutator: Func) -> &mut Self
407 where
408 Func: Fn(&mut LinkButton) -> &mut LinkButton,
409 {
410 let mut button = LinkButton::new();
411 button_mutator(&mut button);
412 self.components.push(NonCompositeComponent::Button(
413 button.to_serializable_button()
414 ));
415 self
416 }
417
418 pub fn regular_button<Func>(&mut self, button_mutator: Func) -> &mut Self
419 where
420 Func: Fn(&mut RegularButton) -> &mut RegularButton,
421 {
422 let mut button = RegularButton::new();
423 button_mutator(&mut button);
424 self.components.push(NonCompositeComponent::Button(
425 button.to_serializable_button()
426 ));
427 self
428 }
429}
430
431#[derive(Debug, Clone)]
432pub enum NonLinkButtonStyle {
433 Primary,
434 Secondary,
435 Success,
436 Danger,
437}
438
439impl NonLinkButtonStyle {
440 fn get_button_style(&self) -> ButtonStyles {
441 match *self {
442 NonLinkButtonStyle::Primary => ButtonStyles::Primary,
443 NonLinkButtonStyle::Secondary => ButtonStyles::Secondary,
444 NonLinkButtonStyle::Success => ButtonStyles::Success,
445 NonLinkButtonStyle::Danger => ButtonStyles::Danger,
446 }
447 }
448}
449
450#[derive(Debug)]
454enum ButtonStyles {
455 Primary,
456 Secondary,
457 Success,
458 Danger,
459 Link,
460}
461
462impl ButtonStyles {
463 fn value(&self) -> i32 {
465 match *self {
466 ButtonStyles::Primary => 1,
467 ButtonStyles::Secondary => 2,
468 ButtonStyles::Success => 3,
469 ButtonStyles::Danger => 4,
470 ButtonStyles::Link => 5,
471 }
472 }
473}
474
475impl Serialize for ButtonStyles {
476 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
477 where
478 S: Serializer,
479 {
480 serializer.serialize_i32(self.value())
481 }
482}
483
484#[derive(Serialize, Debug, Clone)]
485pub struct PartialEmoji {
486 pub id: Snowflake,
487 pub name: String,
488 pub animated: Option<bool>,
489}
490
491#[derive(Serialize, Debug)]
493struct Button {
494 #[serde(rename = "type")]
495 pub component_type: i8,
496 pub style: Option<ButtonStyles>,
497 pub label: Option<String>,
498 pub emoji: Option<PartialEmoji>,
499 pub custom_id: Option<String>,
500 pub url: Option<String>,
501 pub disabled: Option<bool>,
502}
503
504impl Button {
505 fn new(
506 style: Option<ButtonStyles>,
507 label: Option<String>,
508 emoji: Option<PartialEmoji>,
509 url: Option<String>,
510 custom_id: Option<String>,
511 disabled: Option<bool>,
512 ) -> Self {
513 Self {
514 component_type: 2,
515 style,
516 label,
517 emoji,
518 url,
519 custom_id,
520 disabled,
521 }
522 }
523}
524
525#[derive(Debug)]
527struct ButtonCommonBase {
528 pub label: Option<String>,
529 pub emoji: Option<PartialEmoji>,
530 pub disabled: Option<bool>,
531}
532
533impl ButtonCommonBase {
534 fn new(label: Option<String>, emoji: Option<PartialEmoji>, disabled: Option<bool>) -> Self {
535 ButtonCommonBase {
536 label,
537 emoji,
538 disabled,
539 }
540 }
541 fn label(&mut self, label: &str) -> &mut Self {
542 self.label = Some(label.to_string());
543 self
544 }
545
546 fn emoji(&mut self, emoji_id: Snowflake, name: &str, animated: bool) -> &mut Self {
547 self.emoji = Some(PartialEmoji {
548 id: emoji_id,
549 name: name.to_string(),
550 animated: Some(animated),
551 });
552 self
553 }
554
555 fn disabled(&mut self, disabled: bool) -> &mut Self {
556 self.disabled = Some(disabled);
557 self
558 }
559}
560
561macro_rules! button_base_delegation {
564 ($base:ident) => {
565 pub fn emoji(&mut self, emoji_id: &str, name: &str, animated: bool) -> &mut Self {
566 self.$base.emoji(emoji_id.to_string(), name, animated);
567 self
568 }
569
570 pub fn disabled(&mut self, disabled: bool) -> &mut Self {
571 self.$base.disabled(disabled);
572 self
573 }
574
575 pub fn label(&mut self, label: &str) -> &mut Self {
576 self.$base.label(label);
577 self
578 }
579 };
580}
581
582#[derive(Debug)]
583pub struct LinkButton {
584 button_base: ButtonCommonBase,
585 url: Option<String>,
586}
587
588impl LinkButton {
589 fn new() -> Self {
590 LinkButton {
591 button_base: ButtonCommonBase::new(None, None, None),
592 url: None,
593 }
594 }
595
596 pub fn url(&mut self, url: &str) -> &mut Self {
597 self.url = Some(url.to_string());
598 self
599 }
600
601 button_base_delegation!(button_base);
602}
603
604pub struct RegularButton {
605 button_base: ButtonCommonBase,
606 custom_id: Option<String>,
607 style: Option<NonLinkButtonStyle>,
608}
609
610impl RegularButton {
611 fn new() -> Self {
612 RegularButton {
613 button_base: ButtonCommonBase::new(None, None, None),
614 custom_id: None,
615 style: None,
616 }
617 }
618
619 pub fn custom_id(&mut self, custom_id: &str) -> &mut Self {
620 self.custom_id = Some(custom_id.to_string());
621 self
622 }
623
624 pub fn style(&mut self, style: NonLinkButtonStyle) -> &mut Self {
625 self.style = Some(style);
626 self
627 }
628
629 button_base_delegation!(button_base);
630}
631
632trait ToSerializableButton {
633 fn to_serializable_button(&self) -> Button;
634}
635
636impl ToSerializableButton for LinkButton {
637 fn to_serializable_button(&self) -> Button {
638 Button::new(
639 Some(ButtonStyles::Link),
640 self.button_base.label.clone(),
641 self.button_base.emoji.clone(),
642 self.url.clone(),
643 None,
644 self.button_base.disabled,
645 )
646 }
647}
648
649impl ToSerializableButton for RegularButton {
650 fn to_serializable_button(&self) -> Button {
651 Button::new(
652 self.style.clone().map(|s| s.get_button_style()),
653 self.button_base.label.clone(),
654 self.button_base.emoji.clone(),
655 None,
656 self.custom_id.clone(),
657 self.button_base.disabled,
658 )
659 }
660}
661
662pub(crate) trait DiscordApiCompatible {
668 fn check_compatibility(&self, context: &mut MessageContext) -> Result<(), String>;
669}
670
671impl DiscordApiCompatible for NonCompositeComponent {
672 fn check_compatibility(&self, context: &mut MessageContext) -> Result<(), String> {
673 match self {
674 NonCompositeComponent::Button(b) => b.check_compatibility(context),
675 }
676 }
677}
678
679fn bool_to_result<E>(b: bool, err: E) -> Result<(), E> {
680 if b {
681 Ok(())
682 } else {
683 Err(err)
684 }
685}
686
687impl DiscordApiCompatible for Button {
688 fn check_compatibility(&self, context: &mut MessageContext) -> Result<(), String> {
689 if self.label.is_some() && self.label.as_ref().unwrap().len() > Message::label_max_len() {
690 return Err(format!(
691 "Label length exceeds {} characters",
692 Message::label_max_len()
693 ));
694 }
695
696 return match self.style {
697 None => Err("Button style must be set!".to_string()),
698 Some(ButtonStyles::Link) => {
699 if self.url.is_none() {
700 Err("Url of a Link button must be set!".to_string())
701 } else {
702 Ok(())
703 }
704 }
705 Some(ButtonStyles::Danger)
707 | Some(ButtonStyles::Primary)
708 | Some(ButtonStyles::Success)
709 | Some(ButtonStyles::Secondary) => {
710 return if let Some(id) = self.custom_id.as_ref() {
711 bool_to_result(
712 id.len() <= Message::custom_id_max_len(),
713 format!(
714 "Custom ID length exceeds {} characters",
715 Message::custom_id_max_len()
716 ),
717 )
718 .and(bool_to_result(
719 context.register_custom_id(id),
720 format!(
721 "Attempt to use the same custom ID ({}) twice! (buttonLabel: {:?})",
722 id, self.label
723 ),
724 ))
725 } else {
726 Err("Custom ID of a NonLink button must be set!".to_string())
727 };
728 }
729 };
730 }
731}
732
733impl DiscordApiCompatible for ActionRow {
734 fn check_compatibility(&self, context: &mut MessageContext) -> Result<(), String> {
735 self.components.iter().fold(Ok(()), |acc, component| {
736 acc.and(component.check_compatibility(context))
737 })
738 }
739}
740
741impl DiscordApiCompatible for Message {
742 fn check_compatibility(&self, context: &mut MessageContext) -> Result<(), String> {
743 if self.action_rows.len() > Self::max_action_row_count() {
744 return Err(format!(
745 "Action row count exceeded {} (maximum)",
746 Message::max_action_row_count()
747 ));
748 }
749
750 self.action_rows
751 .iter()
752 .fold(Ok(()), |acc, row| acc.and(row.check_compatibility(context)))
753 }
754}