serenity/utils/message_builder.rs
1use std::fmt::{self as fmt, Write};
2use std::ops::Add;
3
4use crate::model::guild::Emoji;
5use crate::model::id::{ChannelId, RoleId, UserId};
6use crate::model::mention::Mentionable;
7
8/// The Message Builder is an ergonomic utility to easily build a message, by adding text and
9/// mentioning mentionable structs.
10///
11/// The finalized value can be accessed via [`Self::build`] or the inner value.
12///
13/// # Examples
14///
15/// Build a message, mentioning a [`Self::user`] and an [`Self::emoji`], and retrieving the
16/// value:
17///
18/// ```rust,no_run
19/// # use serenity::model::prelude::*;
20/// #
21/// # fn run(user: UserId, emoji: Emoji) {
22/// #
23/// use serenity::utils::MessageBuilder;
24///
25/// // assuming an `emoji` and `user` have already been bound
26///
27/// let content = MessageBuilder::new()
28/// .push("You sent a message, ")
29/// .mention(&user)
30/// .push("! ")
31/// .emoji(&emoji)
32/// .build();
33/// # }
34/// ```
35#[derive(Clone, Debug, Default)]
36pub struct MessageBuilder(pub String);
37
38impl MessageBuilder {
39 /// Creates a new, empty builder.
40 ///
41 /// # Examples
42 ///
43 /// Create a new [`MessageBuilder`]:
44 ///
45 /// ```rust
46 /// use serenity::utils::MessageBuilder;
47 ///
48 /// let message = MessageBuilder::new();
49 ///
50 /// // alternatively:
51 /// let message = MessageBuilder::default();
52 /// ```
53 #[must_use]
54 pub fn new() -> MessageBuilder {
55 MessageBuilder::default()
56 }
57
58 /// Pulls the inner value out of the builder.
59 ///
60 /// # Examples
61 ///
62 /// Create a string mentioning a channel by Id, and then suffixing `"!"`, and finally building
63 /// it to retrieve the inner String:
64 ///
65 /// ```rust
66 /// use serenity::model::id::ChannelId;
67 /// use serenity::utils::MessageBuilder;
68 ///
69 /// let channel_id = ChannelId::new(81384788765712384);
70 ///
71 /// let content = MessageBuilder::new().channel(channel_id).push("!").build();
72 ///
73 /// assert_eq!(content, "<#81384788765712384>!");
74 /// ```
75 ///
76 /// This is equivalent to simply retrieving the tuple struct's first value:
77 ///
78 /// ```rust
79 /// use serenity::utils::MessageBuilder;
80 ///
81 /// let mut content = MessageBuilder::new();
82 /// content.push("test");
83 ///
84 /// assert_eq!(content.build(), "test");
85 /// ```
86 pub fn build(&mut self) -> String {
87 self.clone().0
88 }
89
90 /// Mentions the [`GuildChannel`] in the built message.
91 ///
92 /// This accepts anything that converts _into_ a [`ChannelId`]. Refer to [`ChannelId`]'s
93 /// documentation for more information.
94 ///
95 /// Refer to [`ChannelId`]'s [Display implementation] for more information on how this is
96 /// formatted.
97 ///
98 /// # Examples
99 ///
100 /// Mentioning a [`Channel`] by Id:
101 ///
102 /// ```rust
103 /// use serenity::model::id::ChannelId;
104 /// use serenity::utils::MessageBuilder;
105 ///
106 /// let channel_id = ChannelId::new(81384788765712384);
107 ///
108 /// let content = MessageBuilder::new().push("The channel is: ").channel(channel_id).build();
109 ///
110 /// assert_eq!(content, "The channel is: <#81384788765712384>");
111 /// ```
112 ///
113 /// [`Channel`]: crate::model::channel::Channel
114 /// [`GuildChannel`]: crate::model::channel::GuildChannel
115 /// [Display implementation]: ChannelId#impl-Display
116 #[inline]
117 pub fn channel<C: Into<ChannelId>>(&mut self, channel: C) -> &mut Self {
118 self.channel_(channel.into())
119 }
120
121 fn channel_(&mut self, channel: ChannelId) -> &mut Self {
122 self.push_(&channel.mention());
123 self
124 }
125
126 /// Displays the given emoji in the built message.
127 ///
128 /// Refer to [`Emoji`]s [Display implementation] for more information on how this is formatted.
129 ///
130 /// # Examples
131 ///
132 /// Mention an emoji in a message's content:
133 ///
134 /// ```rust
135 /// # use serenity::json::{json, from_value};
136 /// # use serenity::model::guild::Emoji;
137 /// # use serenity::model::id::EmojiId;
138 /// # use serenity::utils::MessageBuilder;
139 ///
140 /// # let emoji = from_value::<Emoji>(json!({
141 /// # "id": EmojiId::new(302516740095606785),
142 /// # "name": "smugAnimeFace",
143 /// # })).unwrap();
144 ///
145 /// let message = MessageBuilder::new().push("foo ").emoji(&emoji).push(".").build();
146 ///
147 /// assert_eq!(message, "foo <:smugAnimeFace:302516740095606785>.");
148 /// ```
149 ///
150 /// [Display implementation]: crate::model::guild::Emoji#impl-Display
151 pub fn emoji(&mut self, emoji: &Emoji) -> &mut Self {
152 self.push_(&emoji);
153 self
154 }
155
156 /// Mentions something that implements the [`Mentionable`] trait.
157 pub fn mention<M: Mentionable>(&mut self, item: &M) -> &mut Self {
158 self.push_(&item.mention());
159 self
160 }
161
162 /// Pushes a string to the internal message content.
163 ///
164 /// Note that this does not mutate either the given data or the internal message content in
165 /// anyway prior to appending the given content to the internal message.
166 ///
167 /// # Examples
168 ///
169 /// ```rust
170 /// use serenity::utils::MessageBuilder;
171 ///
172 /// let mut message = MessageBuilder::new();
173 /// message.push("test");
174 ///
175 /// assert_eq!(
176 /// {
177 /// message.push("ing");
178 /// message.build()
179 /// },
180 /// "testing"
181 /// );
182 /// ```
183 #[inline]
184 pub fn push(&mut self, content: impl Into<Content>) -> &mut Self {
185 self.push_(&content.into())
186 }
187
188 #[inline]
189 fn push_<C: std::fmt::Display + ?Sized>(&mut self, content: &C) -> &mut Self {
190 write!(self.0, "{content}").unwrap();
191
192 self
193 }
194
195 /// Pushes a codeblock to the content, with optional syntax highlighting.
196 ///
197 /// # Examples
198 ///
199 /// Pushing a Rust codeblock:
200 ///
201 /// ```rust,ignore
202 /// use serenity::utils::MessageBuilder;
203 ///
204 /// let code = r#"
205 /// fn main() {
206 /// println!("Hello, world!");
207 /// }
208 /// "#;
209 ///
210 /// let content = MessageBuilder::new()
211 /// .push_codeblock(code, Some("rust"))
212 /// .build();
213 ///
214 /// let expected = r#"```rust
215 /// fn main() {
216 /// println!("Hello, world!");
217 /// }
218 /// ```"#;
219 ///
220 /// assert_eq!(content, expected);
221 /// ```
222 ///
223 /// Pushing a codeblock without a language:
224 /// ```rust
225 /// use serenity::utils::MessageBuilder;
226 ///
227 /// let content = MessageBuilder::new()
228 /// .push_codeblock("hello", None)
229 /// .build();
230 ///
231 /// assert_eq!(content, "```\nhello\n```");
232 /// ```
233 pub fn push_codeblock(
234 &mut self,
235 content: impl Into<Content>,
236 language: Option<&str>,
237 ) -> &mut Self {
238 self.0.push_str("```");
239
240 if let Some(language) = language {
241 self.0.push_str(language);
242 }
243
244 self.0.push('\n');
245 self.push_(&content.into());
246 self.0.push_str("\n```");
247
248 self
249 }
250
251 /// Pushes inlined monospaced text to the content.
252 ///
253 /// # Examples
254 ///
255 /// Display a server configuration value to the user:
256 ///
257 /// ```rust
258 /// use serenity::utils::MessageBuilder;
259 ///
260 /// let key = "prefix";
261 /// let value = "&";
262 ///
263 /// let content = MessageBuilder::new()
264 /// .push("The setting ")
265 /// .push_mono(key)
266 /// .push(" for this server is ")
267 /// .push_mono(value)
268 /// .push(".")
269 /// .build();
270 ///
271 /// let expected = format!("The setting `{}` for this server is `{}`.", key, value);
272 ///
273 /// assert_eq!(content, expected);
274 /// ```
275 pub fn push_mono(&mut self, content: impl Into<Content>) -> &mut Self {
276 self.0.push('`');
277 self.push_(&content.into());
278 self.0.push('`');
279
280 self
281 }
282
283 /// Pushes inlined italicized text to the content.
284 ///
285 /// # Examples
286 ///
287 /// Emphasize information to the user:
288 ///
289 /// ```rust
290 /// use serenity::utils::MessageBuilder;
291 ///
292 /// let content = MessageBuilder::new()
293 /// .push("You don't ")
294 /// .push_italic("always need")
295 /// .push(" to italicize ")
296 /// .push_italic("everything")
297 /// .push(".")
298 /// .build();
299 ///
300 /// let expected = "You don't _always need_ to italicize _everything_.";
301 ///
302 /// assert_eq!(content, expected);
303 /// ```
304 pub fn push_italic(&mut self, content: impl Into<Content>) -> &mut Self {
305 self.0.push('_');
306 self.push_(&content.into());
307 self.0.push('_');
308
309 self
310 }
311
312 /// Pushes an inline bold text to the content.
313 pub fn push_bold(&mut self, content: impl Into<Content>) -> &mut Self {
314 self.0.push_str("**");
315 self.push_(&content.into());
316 self.0.push_str("**");
317
318 self
319 }
320
321 /// Pushes an underlined inline text to the content.
322 pub fn push_underline(&mut self, content: impl Into<Content>) -> &mut Self {
323 self.0.push_str("__");
324 self.push_(&content.into());
325 self.0.push_str("__");
326
327 self
328 }
329
330 /// Pushes a strikethrough inline text to the content.
331 pub fn push_strike(&mut self, content: impl Into<Content>) -> &mut Self {
332 self.0.push_str("~~");
333 self.push_(&content.into());
334 self.0.push_str("~~");
335
336 self
337 }
338
339 /// Pushes a spoiler'd inline text to the content.
340 pub fn push_spoiler(&mut self, content: impl Into<Content>) -> &mut Self {
341 self.0.push_str("||");
342 self.push_(&content.into());
343 self.0.push_str("||");
344
345 self
346 }
347
348 /// Pushes a quoted inline text to the content
349 pub fn push_quote(&mut self, content: impl Into<Content>) -> &mut Self {
350 self.0.push_str("> ");
351 self.push_(&content.into());
352
353 self
354 }
355
356 /// Pushes the given text with a newline appended to the content.
357 ///
358 /// # Examples
359 ///
360 /// Push content and then append a newline:
361 ///
362 /// ```rust
363 /// use serenity::utils::MessageBuilder;
364 ///
365 /// let content = MessageBuilder::new().push_line("hello").push("world").build();
366 ///
367 /// assert_eq!(content, "hello\nworld");
368 /// ```
369 pub fn push_line(&mut self, content: impl Into<Content>) -> &mut Self {
370 self.push(content);
371 self.0.push('\n');
372
373 self
374 }
375
376 /// Pushes inlined monospace text with an added newline to the content.
377 ///
378 /// # Examples
379 ///
380 /// Push content and then append a newline:
381 ///
382 /// ```rust
383 /// use serenity::utils::MessageBuilder;
384 ///
385 /// let content = MessageBuilder::new().push_mono_line("hello").push("world").build();
386 ///
387 /// assert_eq!(content, "`hello`\nworld");
388 /// ```
389 pub fn push_mono_line(&mut self, content: impl Into<Content>) -> &mut Self {
390 self.push_mono(content);
391 self.0.push('\n');
392
393 self
394 }
395
396 /// Pushes an inlined italicized text with an added newline to the content.
397 ///
398 /// # Examples
399 ///
400 /// Push content and then append a newline:
401 ///
402 /// ```rust
403 /// use serenity::utils::MessageBuilder;
404 ///
405 /// let content = MessageBuilder::new().push_italic_line("hello").push("world").build();
406 ///
407 /// assert_eq!(content, "_hello_\nworld");
408 /// ```
409 pub fn push_italic_line(&mut self, content: impl Into<Content>) -> &mut Self {
410 self.push_italic(content);
411 self.0.push('\n');
412
413 self
414 }
415
416 /// Pushes an inline bold text with an added newline to the content.
417 ///
418 /// # Examples
419 ///
420 /// Push content and then append a newline:
421 ///
422 /// ```rust
423 /// use serenity::utils::MessageBuilder;
424 ///
425 /// let content = MessageBuilder::new().push_bold_line("hello").push("world").build();
426 ///
427 /// assert_eq!(content, "**hello**\nworld");
428 /// ```
429 pub fn push_bold_line(&mut self, content: impl Into<Content>) -> &mut Self {
430 self.push_bold(content);
431 self.0.push('\n');
432
433 self
434 }
435
436 /// Pushes an underlined inline text with an added newline to the content.
437 ///
438 /// # Examples
439 ///
440 /// Push content and then append a newline:
441 ///
442 /// ```rust
443 /// use serenity::utils::MessageBuilder;
444 ///
445 /// let content = MessageBuilder::new().push_underline_line("hello").push("world").build();
446 ///
447 /// assert_eq!(content, "__hello__\nworld");
448 /// ```
449 pub fn push_underline_line(&mut self, content: impl Into<Content>) -> &mut Self {
450 self.push_underline(content);
451 self.0.push('\n');
452
453 self
454 }
455
456 /// Pushes a strikethrough inline text with a newline added to the content.
457 ///
458 /// # Examples
459 ///
460 /// Push content and then append a newline:
461 ///
462 /// ```rust
463 /// use serenity::utils::MessageBuilder;
464 ///
465 /// let content = MessageBuilder::new().push_strike_line("hello").push("world").build();
466 ///
467 /// assert_eq!(content, "~~hello~~\nworld");
468 /// ```
469 pub fn push_strike_line(&mut self, content: impl Into<Content>) -> &mut Self {
470 self.push_strike(content);
471 self.0.push('\n');
472
473 self
474 }
475
476 /// Pushes a spoiler'd inline text with a newline added to the content.
477 ///
478 /// # Examples
479 ///
480 /// Push content and then append a newline:
481 ///
482 /// ```rust
483 /// use serenity::utils::MessageBuilder;
484 ///
485 /// let content = MessageBuilder::new().push_spoiler_line("hello").push("world").build();
486 ///
487 /// assert_eq!(content, "||hello||\nworld");
488 /// ```
489 pub fn push_spoiler_line(&mut self, content: impl Into<Content>) -> &mut Self {
490 self.push_spoiler(content);
491 self.0.push('\n');
492
493 self
494 }
495
496 /// Pushes a quoted inline text to the content
497 ///
498 /// # Examples
499 ///
500 /// Push content and then append a newline:
501 ///
502 /// ```rust
503 /// use serenity::utils::MessageBuilder;
504 ///
505 /// let content = MessageBuilder::new().push_quote_line("hello").push("world").build();
506 ///
507 /// assert_eq!(content, "> hello\nworld");
508 /// ```
509 pub fn push_quote_line(&mut self, content: impl Into<Content>) -> &mut Self {
510 self.push_quote(content);
511 self.0.push('\n');
512
513 self
514 }
515
516 /// Pushes text to your message, but normalizing content - that means ensuring that there's no
517 /// unwanted formatting, mention spam etc.
518 pub fn push_safe(&mut self, content: impl Into<Content>) -> &mut Self {
519 {
520 let mut c = content.into();
521 c.inner =
522 normalize(&c.inner).replace('*', "\\*").replace('`', "\\`").replace('_', "\\_");
523
524 self.push_(&c);
525 }
526
527 self
528 }
529
530 /// Pushes a code-block to your message normalizing content.
531 pub fn push_codeblock_safe(
532 &mut self,
533 content: impl Into<Content>,
534 language: Option<&str>,
535 ) -> &mut Self {
536 self.0.push_str("```");
537
538 if let Some(language) = language {
539 self.0.push_str(language);
540 }
541
542 self.0.push('\n');
543 {
544 let mut c = content.into();
545 c.inner = normalize(&c.inner).replace("```", " ");
546 self.push_(&c);
547 }
548 self.0.push_str("\n```");
549
550 self
551 }
552
553 /// Pushes an inline monospaced text to the content normalizing content.
554 pub fn push_mono_safe(&mut self, content: impl Into<Content>) -> &mut Self {
555 self.0.push('`');
556 {
557 let mut c = content.into();
558 c.inner = normalize(&c.inner).replace('`', "'");
559 self.push_(&c);
560 }
561 self.0.push('`');
562
563 self
564 }
565
566 /// Pushes an inline italicized text to the content normalizing content.
567 pub fn push_italic_safe(&mut self, content: impl Into<Content>) -> &mut Self {
568 self.0.push('_');
569 {
570 let mut c = content.into();
571 c.inner = normalize(&c.inner).replace('_', " ");
572 self.push_(&c);
573 }
574 self.0.push('_');
575
576 self
577 }
578
579 /// Pushes an inline bold text to the content normalizing content.
580 pub fn push_bold_safe(&mut self, content: impl Into<Content>) -> &mut Self {
581 self.0.push_str("**");
582 {
583 let mut c = content.into();
584 c.inner = normalize(&c.inner).replace("**", " ");
585 self.push_(&c);
586 }
587 self.0.push_str("**");
588
589 self
590 }
591
592 /// Pushes an underlined inline text to the content normalizing content.
593 pub fn push_underline_safe(&mut self, content: impl Into<Content>) -> &mut Self {
594 self.0.push_str("__");
595 {
596 let mut c = content.into();
597 c.inner = normalize(&c.inner).replace("__", " ");
598 self.push_(&c);
599 }
600 self.0.push_str("__");
601
602 self
603 }
604
605 /// Pushes a strikethrough inline text to the content normalizing content.
606 pub fn push_strike_safe(&mut self, content: impl Into<Content>) -> &mut Self {
607 self.0.push_str("~~");
608 {
609 let mut c = content.into();
610 c.inner = normalize(&c.inner).replace("~~", " ");
611 self.push_(&c);
612 }
613 self.0.push_str("~~");
614
615 self
616 }
617
618 /// Pushes a spoiler'd inline text to the content normalizing content.
619 pub fn push_spoiler_safe(&mut self, content: impl Into<Content>) -> &mut Self {
620 self.0.push_str("||");
621 {
622 let mut c = content.into();
623 c.inner = normalize(&c.inner).replace("||", " ");
624 self.push_(&c);
625 }
626 self.0.push_str("||");
627
628 self
629 }
630
631 /// Pushes a quoted inline text to the content normalizing content.
632 pub fn push_quote_safe(&mut self, content: impl Into<Content>) -> &mut Self {
633 self.0.push_str("> ");
634 {
635 let mut c = content.into();
636 c.inner = normalize(&c.inner).replace("> ", " ");
637 self.push_(&c);
638 }
639
640 self
641 }
642
643 /// Pushes text with a newline appended to the content normalizing content.
644 ///
645 /// # Examples
646 ///
647 /// Push content and then append a newline:
648 ///
649 /// ```rust
650 /// use serenity::utils::MessageBuilder;
651 ///
652 /// let content =
653 /// MessageBuilder::new().push_line_safe("Hello @everyone").push("How are you?").build();
654 ///
655 /// assert_eq!(content, "Hello @\u{200B}everyone\nHow are you?");
656 /// ```
657 pub fn push_line_safe(&mut self, content: impl Into<Content>) -> &mut Self {
658 self.push_safe(content);
659 self.0.push('\n');
660
661 self
662 }
663
664 /// Pushes an inline monospaced text with added newline to the content normalizing content.
665 ///
666 /// # Examples
667 ///
668 /// Push content and then append a newline:
669 ///
670 /// ```rust
671 /// use serenity::utils::MessageBuilder;
672 ///
673 /// let content =
674 /// MessageBuilder::new().push_mono_line_safe("`hello @everyone`").push("world").build();
675 ///
676 /// assert_eq!(content, "`'hello @\u{200B}everyone'`\nworld");
677 /// ```
678 pub fn push_mono_line_safe(&mut self, content: impl Into<Content>) -> &mut Self {
679 self.push_mono_safe(content);
680 self.0.push('\n');
681
682 self
683 }
684
685 /// Pushes an inline italicized text with added newline to the content normalizing content.
686 ///
687 /// # Examples
688 ///
689 /// Push content and then append a newline:
690 ///
691 /// ```rust
692 /// use serenity::utils::MessageBuilder;
693 ///
694 /// let content =
695 /// MessageBuilder::new().push_italic_line_safe("@everyone").push("Isn't a mention.").build();
696 ///
697 /// assert_eq!(content, "_@\u{200B}everyone_\nIsn't a mention.");
698 /// ```
699 pub fn push_italic_line_safe(&mut self, content: impl Into<Content>) -> &mut Self {
700 self.push_italic_safe(content);
701 self.0.push('\n');
702
703 self
704 }
705
706 /// Pushes an inline bold text with added newline to the content normalizing content.
707 ///
708 /// # Examples
709 ///
710 /// Push content and then append a newline:
711 ///
712 /// ```rust
713 /// use serenity::utils::MessageBuilder;
714 ///
715 /// let content =
716 /// MessageBuilder::new().push_bold_line_safe("@everyone").push("Isn't a mention.").build();
717 ///
718 /// assert_eq!(content, "**@\u{200B}everyone**\nIsn't a mention.");
719 /// ```
720 pub fn push_bold_line_safe(&mut self, content: impl Into<Content>) -> &mut Self {
721 self.push_bold_safe(content);
722 self.0.push('\n');
723
724 self
725 }
726
727 /// Pushes an underlined inline text with added newline to the content normalizing content.
728 ///
729 /// # Examples
730 ///
731 /// Push content and then append a newline:
732 ///
733 /// ```rust
734 /// use serenity::utils::MessageBuilder;
735 ///
736 /// let content = MessageBuilder::new()
737 /// .push_underline_line_safe("@everyone")
738 /// .push("Isn't a mention.")
739 /// .build();
740 ///
741 /// assert_eq!(content, "__@\u{200B}everyone__\nIsn't a mention.");
742 /// ```
743 pub fn push_underline_line_safe(&mut self, content: impl Into<Content>) -> &mut Self {
744 self.push_underline_safe(content);
745 self.0.push('\n');
746
747 self
748 }
749
750 /// Pushes a strikethrough inline text with added newline to the content normalizing content.
751 ///
752 /// # Examples
753 ///
754 /// Push content and then append a newline:
755 ///
756 /// ```rust
757 /// use serenity::utils::MessageBuilder;
758 ///
759 /// let content =
760 /// MessageBuilder::new().push_strike_line_safe("@everyone").push("Isn't a mention.").build();
761 ///
762 /// assert_eq!(content, "~~@\u{200B}everyone~~\nIsn't a mention.");
763 /// ```
764 pub fn push_strike_line_safe(&mut self, content: impl Into<Content>) -> &mut Self {
765 self.push_strike_safe(content);
766 self.0.push('\n');
767
768 self
769 }
770
771 /// Pushes a spoiler'd inline text with added newline to the content normalizing content.
772 ///
773 /// # Examples
774 ///
775 /// Push content and then append a newline:
776 ///
777 /// ```rust
778 /// use serenity::utils::MessageBuilder;
779 ///
780 /// let content =
781 /// MessageBuilder::new().push_spoiler_line_safe("@everyone").push("Isn't a mention.").build();
782 ///
783 /// assert_eq!(content, "||@\u{200B}everyone||\nIsn't a mention.");
784 /// ```
785 pub fn push_spoiler_line_safe(&mut self, content: impl Into<Content>) -> &mut Self {
786 self.push_spoiler_safe(content);
787 self.0.push('\n');
788
789 self
790 }
791
792 /// Pushes a quoted inline text with added newline to the content normalizing content.
793 ///
794 /// # Examples
795 ///
796 /// Push content and then append a newline:
797 ///
798 /// ```rust
799 /// use serenity::utils::MessageBuilder;
800 ///
801 /// let content =
802 /// MessageBuilder::new().push_quote_line_safe("@everyone").push("Isn't a mention.").build();
803 ///
804 /// assert_eq!(content, "> @\u{200B}everyone\nIsn't a mention.");
805 /// ```
806 pub fn push_quote_line_safe(&mut self, content: impl Into<Content>) -> &mut Self {
807 self.push_quote_safe(content);
808 self.0.push('\n');
809
810 self
811 }
812
813 /// Starts a multi-line quote, every push after this one will be quoted
814 pub fn quote_rest(&mut self) -> &mut Self {
815 self.0.push_str("\n>>> ");
816
817 self
818 }
819
820 /// Mentions the [`Role`] in the built message.
821 ///
822 /// This accepts anything that converts _into_ a [`RoleId`]. Refer to [`RoleId`]'s
823 /// documentation for more information.
824 ///
825 /// Refer to [`RoleId`]'s [Display implementation] for more information on how this is
826 /// formatted.
827 ///
828 /// [`Role`]: crate::model::guild::Role
829 /// [Display implementation]: RoleId#impl-Display
830 pub fn role<R: Into<RoleId>>(&mut self, role: R) -> &mut Self {
831 self.push_(&role.into().mention());
832 self
833 }
834
835 /// Mentions the [`User`] in the built message.
836 ///
837 /// This accepts anything that converts _into_ a [`UserId`]. Refer to [`UserId`]'s
838 /// documentation for more information.
839 ///
840 /// Refer to [`UserId`]'s [Display implementation] for more information on how this is
841 /// formatted.
842 ///
843 /// [`User`]: crate::model::user::User
844 /// [Display implementation]: UserId#impl-Display
845 pub fn user<U: Into<UserId>>(&mut self, user: U) -> &mut Self {
846 self.push_(&user.into().mention());
847 self
848 }
849}
850
851impl fmt::Display for MessageBuilder {
852 /// Formats the message builder into a string.
853 ///
854 /// This is done by simply taking the internal value of the tuple-struct and writing it into
855 /// the formatter.
856 ///
857 /// # Examples
858 ///
859 /// Create a message builder, and format it into a string via the `format!`
860 /// macro:
861 ///
862 /// ```rust
863 /// use serenity::utils::MessageBuilder;
864 /// ```
865 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
866 fmt::Display::fmt(&self.0, f)
867 }
868}
869
870/// A trait with additional functionality over the [`MessageBuilder`] for creating content with
871/// additional functionality available only in embeds.
872///
873/// Namely, this allows you to create named links via the non-escaping [`Self::push_named_link`]
874/// method and the escaping [`Self::push_named_link_safe`] method.
875///
876/// # Examples
877///
878/// Make a named link to Rust's GitHub organization:
879///
880/// ```rust
881/// use serenity::utils::{EmbedMessageBuilding, MessageBuilder};
882///
883/// let msg = MessageBuilder::new()
884/// .push_named_link("Rust's GitHub", "https://github.com/rust-lang")
885/// .build();
886///
887/// assert_eq!(msg, "[Rust's GitHub](https://github.com/rust-lang)");
888/// ```
889pub trait EmbedMessageBuilding {
890 /// Pushes a named link to a message, intended for use in embeds.
891 ///
892 /// # Examples
893 ///
894 /// Make a simple link to Rust's homepage for use in an embed:
895 ///
896 /// ```rust
897 /// use serenity::utils::{EmbedMessageBuilding, MessageBuilder};
898 ///
899 /// let mut msg = MessageBuilder::new();
900 /// msg.push("Rust's website: ");
901 /// msg.push_named_link("Homepage", "https://rust-lang.org");
902 /// let content = msg.build();
903 ///
904 /// assert_eq!(content, "Rust's website: [Homepage](https://rust-lang.org)");
905 /// ```
906 fn push_named_link(&mut self, name: impl Into<Content>, url: impl Into<Content>) -> &mut Self;
907
908 /// Pushes a named link intended for use in an embed, but with a normalized name to avoid
909 /// escaping issues.
910 ///
911 /// Refer to [`Self::push_named_link`] for more information.
912 ///
913 /// # Examples
914 ///
915 /// ```rust
916 /// use serenity::utils::{EmbedMessageBuilding, MessageBuilder};
917 ///
918 /// let mut msg = MessageBuilder::new();
919 /// msg.push("A weird website name: ");
920 /// msg.push_named_link_safe("Try to ] break links (](", "https://rust-lang.org");
921 /// let content = msg.build();
922 ///
923 /// assert_eq!(content, "A weird website name: [Try to break links ( (](https://rust-lang.org)");
924 /// ```
925 fn push_named_link_safe(
926 &mut self,
927 name: impl Into<Content>,
928 url: impl Into<Content>,
929 ) -> &mut Self;
930}
931
932impl EmbedMessageBuilding for MessageBuilder {
933 fn push_named_link(&mut self, name: impl Into<Content>, url: impl Into<Content>) -> &mut Self {
934 write!(self.0, "[{}]({})", name.into(), url.into()).unwrap();
935
936 self
937 }
938
939 fn push_named_link_safe(
940 &mut self,
941 name: impl Into<Content>,
942 url: impl Into<Content>,
943 ) -> &mut Self {
944 self.0.push('[');
945 {
946 let mut c = name.into();
947 c.inner = normalize(&c.inner).replace(']', " ");
948 self.push_(&c);
949 }
950 self.0.push_str("](");
951 {
952 let mut c = url.into();
953 c.inner = normalize(&c.inner).replace(')', " ");
954 self.push_(&c);
955 }
956 self.0.push(')');
957
958 self
959 }
960}
961
962/// Formatting modifiers for MessageBuilder content pushes
963///
964/// Provides an enum of formatting modifiers for a string, for combination with string types and
965/// Content types.
966///
967/// # Examples
968///
969/// Create a new Content type which describes a bold-italic "text":
970///
971/// ```rust,no_run
972/// use serenity::utils::Content;
973/// use serenity::utils::ContentModifier::{Bold, Italic};
974/// let content: Content = Bold + Italic + "text";
975/// ```
976#[non_exhaustive]
977pub enum ContentModifier {
978 Italic,
979 Bold,
980 Strikethrough,
981 Code,
982 Underline,
983 Spoiler,
984}
985
986/// Describes formatting on string content
987#[derive(Clone, Debug, Default)]
988pub struct Content {
989 pub italic: bool,
990 pub bold: bool,
991 pub strikethrough: bool,
992 pub inner: String,
993 pub code: bool,
994 pub underline: bool,
995 pub spoiler: bool,
996}
997
998impl<T: AsRef<str>> Add<T> for Content {
999 type Output = Content;
1000
1001 fn add(mut self, rhs: T) -> Content {
1002 self.inner += rhs.as_ref();
1003
1004 self
1005 }
1006}
1007
1008impl<T: AsRef<str>> Add<T> for ContentModifier {
1009 type Output = Content;
1010
1011 fn add(self, rhs: T) -> Content {
1012 let mut nc = self.to_content();
1013 nc.inner += rhs.as_ref();
1014 nc
1015 }
1016}
1017
1018impl Add<ContentModifier> for Content {
1019 type Output = Content;
1020
1021 fn add(mut self, rhs: ContentModifier) -> Content {
1022 self.apply(&rhs);
1023
1024 self
1025 }
1026}
1027
1028impl Add<ContentModifier> for ContentModifier {
1029 type Output = Content;
1030
1031 fn add(self, rhs: ContentModifier) -> Content {
1032 let mut nc = self.to_content();
1033 nc.apply(&rhs);
1034
1035 nc
1036 }
1037}
1038
1039impl ContentModifier {
1040 fn to_content(&self) -> Content {
1041 let mut nc = Content::default();
1042 nc.apply(self);
1043
1044 nc
1045 }
1046}
1047
1048impl Content {
1049 pub fn apply(&mut self, modifier: &ContentModifier) {
1050 match *modifier {
1051 ContentModifier::Italic => {
1052 self.italic = true;
1053 },
1054 ContentModifier::Bold => {
1055 self.bold = true;
1056 },
1057 ContentModifier::Strikethrough => {
1058 self.strikethrough = true;
1059 },
1060 ContentModifier::Code => {
1061 self.code = true;
1062 },
1063 ContentModifier::Underline => {
1064 self.underline = true;
1065 },
1066 ContentModifier::Spoiler => {
1067 self.spoiler = true;
1068 },
1069 }
1070 }
1071}
1072
1073impl std::fmt::Display for Content {
1074 fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1075 if self.spoiler {
1076 fmt.write_str("||")?;
1077 }
1078
1079 if self.bold {
1080 fmt.write_str("**")?;
1081 }
1082
1083 if self.italic {
1084 fmt.write_char('*')?;
1085 }
1086
1087 if self.strikethrough {
1088 fmt.write_str("~~")?;
1089 }
1090
1091 if self.underline {
1092 fmt.write_str("__")?;
1093 }
1094
1095 if self.code {
1096 fmt.write_char('`')?;
1097 }
1098
1099 fmt.write_str(&self.inner)?;
1100
1101 if self.code {
1102 fmt.write_char('`')?;
1103 }
1104
1105 if self.underline {
1106 fmt.write_str("__")?;
1107 }
1108
1109 if self.strikethrough {
1110 fmt.write_str("~~")?;
1111 }
1112
1113 if self.italic {
1114 fmt.write_char('*')?;
1115 }
1116
1117 if self.bold {
1118 fmt.write_str("**")?;
1119 }
1120
1121 if self.spoiler {
1122 fmt.write_str("||")?;
1123 }
1124
1125 Ok(())
1126 }
1127}
1128
1129impl<T: Into<String>> From<T> for Content {
1130 fn from(t: T) -> Content {
1131 Content {
1132 inner: t.into(),
1133 ..Default::default()
1134 }
1135 }
1136}
1137
1138fn normalize(text: &str) -> String {
1139 // Remove invite links and popular scam websites, mostly to prevent the current user from
1140 // triggering various ad detectors and prevent embeds.
1141 text.replace("discord.gg", "discord\u{2024}gg")
1142 .replace("discord.me", "discord\u{2024}me")
1143 .replace("discordlist.net", "discordlist\u{2024}net")
1144 .replace("discordservers.com", "discordservers\u{2024}com")
1145 .replace("discord.com/invite", "discord\u{2024}com/invite")
1146 .replace("discordapp.com/invite", "discordapp\u{2024}com/invite")
1147 // Remove right-to-left override and other similar annoying symbols
1148 .replace([
1149 '\u{202E}', // RTL Override
1150 '\u{200F}', // RTL Mark
1151 '\u{202B}', // RTL Embedding
1152 '\u{200B}', // Zero-width space
1153 '\u{200D}', // Zero-width joiner
1154 '\u{200C}', // Zero-width non-joiner
1155 ], " ")
1156 // Remove everyone and here mentions. Has to be put after ZWS replacement because it
1157 // utilises it itself.
1158 .replace("@everyone", "@\u{200B}everyone")
1159 .replace("@here", "@\u{200B}here")
1160}
1161
1162#[cfg(test)]
1163mod test {
1164 use super::ContentModifier::{Bold, Code, Italic, Spoiler};
1165 use super::MessageBuilder;
1166 use crate::model::prelude::*;
1167
1168 macro_rules! gen {
1169 ($($fn:ident => [$($text:expr => $expected:expr),+]),+) => ({
1170 $(
1171 $(
1172 assert_eq!(MessageBuilder::new().$fn($text).0, $expected);
1173 )+
1174 )+
1175 });
1176 }
1177
1178 #[test]
1179 fn code_blocks() {
1180 let content = MessageBuilder::new().push_codeblock("test", Some("rb")).build();
1181 assert_eq!(content, "```rb\ntest\n```");
1182 }
1183
1184 #[test]
1185 fn safe_content() {
1186 let content = MessageBuilder::new().push_safe("@everyone discord.gg/discord-api").build();
1187 assert_ne!(content, "@everyone discord.gg/discord-api");
1188 }
1189
1190 #[test]
1191 fn no_free_formatting() {
1192 let content = MessageBuilder::new().push_bold_safe("test**test").build();
1193 assert_ne!(content, "**test**test**");
1194 }
1195
1196 #[test]
1197 fn mentions() {
1198 let content_emoji = MessageBuilder::new()
1199 .emoji(&Emoji {
1200 animated: false,
1201 available: true,
1202 id: EmojiId::new(32),
1203 name: "Rohrkatze".to_string(),
1204 managed: false,
1205 require_colons: true,
1206 roles: vec![],
1207 user: None,
1208 })
1209 .build();
1210 let content_mentions = MessageBuilder::new()
1211 .channel(ChannelId::new(1))
1212 .mention(&UserId::new(2))
1213 .role(RoleId::new(3))
1214 .user(UserId::new(4))
1215 .build();
1216 assert_eq!(content_mentions, "<#1><@2><@&3><@4>");
1217 assert_eq!(content_emoji, "<:Rohrkatze:32>");
1218 }
1219
1220 #[test]
1221 fn content() {
1222 let content = Bold + Italic + Code + "Fun!";
1223
1224 assert_eq!(content.to_string(), "***`Fun!`***");
1225
1226 let content = Spoiler + Bold + "Divert your eyes elsewhere";
1227
1228 assert_eq!(content.to_string(), "||**Divert your eyes elsewhere**||");
1229 }
1230
1231 #[test]
1232 fn init() {
1233 assert_eq!(MessageBuilder::new().0, "");
1234 assert_eq!(MessageBuilder::default().0, "");
1235 }
1236
1237 #[test]
1238 fn message_content() {
1239 let message_content = MessageBuilder::new().push(Bold + Italic + Code + "Fun!").build();
1240
1241 assert_eq!(message_content, "***`Fun!`***");
1242 }
1243
1244 #[test]
1245 fn message_content_safe() {
1246 let message_content = MessageBuilder::new().push_safe(Bold + Italic + "test**test").build();
1247
1248 assert_eq!(message_content, "***test\\*\\*test***");
1249 }
1250
1251 #[test]
1252 fn push() {
1253 assert_eq!(MessageBuilder::new().push('a').0, "a");
1254 assert!(MessageBuilder::new().push("").0.is_empty());
1255 }
1256
1257 #[test]
1258 fn push_codeblock() {
1259 let content = &MessageBuilder::new().push_codeblock("foo", None).0.clone();
1260 assert_eq!(content, "```\nfoo\n```");
1261
1262 let content = &MessageBuilder::new().push_codeblock("fn main() { }", Some("rs")).0.clone();
1263 assert_eq!(content, "```rs\nfn main() { }\n```");
1264 }
1265
1266 #[test]
1267 fn push_codeblock_safe() {
1268 assert_eq!(
1269 MessageBuilder::new().push_codeblock_safe("foo", Some("rs")).0,
1270 "```rs\nfoo\n```",
1271 );
1272 assert_eq!(MessageBuilder::new().push_codeblock_safe("", None).0, "```\n\n```",);
1273 assert_eq!(MessageBuilder::new().push_codeblock_safe("1 * 2", None).0, "```\n1 * 2\n```",);
1274 assert_eq!(
1275 MessageBuilder::new().push_codeblock_safe("`1 * 3`", None).0,
1276 "```\n`1 * 3`\n```",
1277 );
1278 assert_eq!(MessageBuilder::new().push_codeblock_safe("```.```", None).0, "```\n . \n```",);
1279 }
1280
1281 #[test]
1282 fn push_safe() {
1283 gen! {
1284 push_safe => [
1285 "" => "",
1286 "foo" => "foo",
1287 "1 * 2" => "1 \\* 2"
1288 ],
1289 push_bold_safe => [
1290 "" => "****",
1291 "foo" => "**foo**",
1292 "*foo*" => "***foo***",
1293 "f*o**o" => "**f*o o**"
1294 ],
1295 push_italic_safe => [
1296 "" => "__",
1297 "foo" => "_foo_",
1298 "f_o_o" => "_f o o_"
1299 ],
1300 push_mono_safe => [
1301 "" => "``",
1302 "foo" => "`foo`",
1303 "asterisk *" => "`asterisk *`",
1304 "`ticks`" => "`'ticks'`"
1305 ],
1306 push_strike_safe => [
1307 "" => "~~~~",
1308 "foo" => "~~foo~~",
1309 "foo ~" => "~~foo ~~~",
1310 "~~foo" => "~~ foo~~",
1311 "~~fo~~o~~" => "~~ fo o ~~"
1312 ],
1313 push_underline_safe => [
1314 "" => "____",
1315 "foo" => "__foo__",
1316 "foo _" => "__foo ___",
1317 "__foo__ bar" => "__ foo bar__"
1318 ],
1319 push_spoiler_safe => [
1320 "" => "||||",
1321 "foo" => "||foo||",
1322 "foo |" => "||foo |||",
1323 "||foo|| bar" =>"|| foo bar||"
1324 ],
1325 push_line_safe => [
1326 "" => "\n",
1327 "foo" => "foo\n",
1328 "1 * 2" => "1 \\* 2\n"
1329 ],
1330 push_mono_line_safe => [
1331 "" => "``\n",
1332 "a ` b `" => "`a ' b '`\n"
1333 ],
1334 push_italic_line_safe => [
1335 "" => "__\n",
1336 "a * c" => "_a * c_\n"
1337 ],
1338 push_bold_line_safe => [
1339 "" => "****\n",
1340 "a ** d" => "**a d**\n"
1341 ],
1342 push_underline_line_safe => [
1343 "" => "____\n",
1344 "a __ e" => "__a e__\n"
1345 ],
1346 push_strike_line_safe => [
1347 "" => "~~~~\n",
1348 "a ~~ f" => "~~a f~~\n"
1349 ],
1350 push_spoiler_line_safe => [
1351 "" => "||||\n",
1352 "a || f" => "||a f||\n"
1353 ]
1354 };
1355 }
1356
1357 #[test]
1358 fn push_unsafe() {
1359 gen! {
1360 push_bold => [
1361 "a" => "**a**",
1362 "" => "****",
1363 '*' => "*****",
1364 "**" => "******"
1365 ],
1366 push_bold_line => [
1367 "" => "****\n",
1368 "foo" => "**foo**\n"
1369 ],
1370 push_italic => [
1371 "a" => "_a_",
1372 "" => "__",
1373 "_" => "___",
1374 "__" => "____"
1375 ],
1376 push_italic_line => [
1377 "" => "__\n",
1378 "foo" => "_foo_\n",
1379 "_?" => "__?_\n"
1380 ],
1381 push_line => [
1382 "" => "\n",
1383 "foo" => "foo\n",
1384 "\n\n" => "\n\n\n",
1385 "\nfoo\n" => "\nfoo\n\n"
1386 ],
1387 push_mono => [
1388 "a" => "`a`",
1389 "" => "``",
1390 "`" => "```",
1391 "``" => "````"
1392 ],
1393 push_mono_line => [
1394 "" => "``\n",
1395 "foo" => "`foo`\n",
1396 "\n" => "`\n`\n",
1397 "`\n`\n" => "``\n`\n`\n"
1398 ],
1399 push_strike => [
1400 "a" => "~~a~~",
1401 "" => "~~~~",
1402 "~" => "~~~~~",
1403 "~~" => "~~~~~~"
1404 ],
1405 push_strike_line => [
1406 "" => "~~~~\n",
1407 "foo" => "~~foo~~\n"
1408 ],
1409 push_underline => [
1410 "a" => "__a__",
1411 "" => "____",
1412 "_" => "_____",
1413 "__" => "______"
1414 ],
1415 push_underline_line => [
1416 "" => "____\n",
1417 "foo" => "__foo__\n"
1418 ],
1419 push_spoiler => [
1420 "a" => "||a||",
1421 "" => "||||",
1422 "|" => "|||||",
1423 "||" => "||||||"
1424 ],
1425 push_spoiler_line => [
1426 "" => "||||\n",
1427 "foo" => "||foo||\n"
1428 ]
1429 };
1430 }
1431
1432 #[test]
1433 fn normalize() {
1434 assert_eq!(super::normalize("@everyone"), "@\u{200B}everyone");
1435 assert_eq!(super::normalize("@here"), "@\u{200B}here");
1436 assert_eq!(super::normalize("discord.gg"), "discord\u{2024}gg");
1437 assert_eq!(super::normalize("discord.me"), "discord\u{2024}me");
1438 assert_eq!(super::normalize("discordlist.net"), "discordlist\u{2024}net");
1439 assert_eq!(super::normalize("discordservers.com"), "discordservers\u{2024}com");
1440 assert_eq!(super::normalize("discord.com/invite"), "discord\u{2024}com/invite");
1441 assert_eq!(super::normalize("\u{202E}"), " ");
1442 assert_eq!(super::normalize("\u{200F}"), " ");
1443 assert_eq!(super::normalize("\u{202B}"), " ");
1444 assert_eq!(super::normalize("\u{200B}"), " ");
1445 assert_eq!(super::normalize("\u{200D}"), " ");
1446 assert_eq!(super::normalize("\u{200C}"), " ");
1447 }
1448}