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}").expect("writing to a string should never fail");
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())
935 .expect("writing to a string should never fail");
936
937 self
938 }
939
940 fn push_named_link_safe(
941 &mut self,
942 name: impl Into<Content>,
943 url: impl Into<Content>,
944 ) -> &mut Self {
945 self.0.push('[');
946 {
947 let mut c = name.into();
948 c.inner = normalize(&c.inner).replace(']', " ");
949 self.push_(&c);
950 }
951 self.0.push_str("](");
952 {
953 let mut c = url.into();
954 c.inner = normalize(&c.inner).replace(')', " ");
955 self.push_(&c);
956 }
957 self.0.push(')');
958
959 self
960 }
961}
962
963/// Formatting modifiers for MessageBuilder content pushes
964///
965/// Provides an enum of formatting modifiers for a string, for combination with string types and
966/// Content types.
967///
968/// # Examples
969///
970/// Create a new Content type which describes a bold-italic "text":
971///
972/// ```rust,no_run
973/// use serenity::utils::Content;
974/// use serenity::utils::ContentModifier::{Bold, Italic};
975/// let content: Content = Bold + Italic + "text";
976/// ```
977#[non_exhaustive]
978pub enum ContentModifier {
979 Italic,
980 Bold,
981 Strikethrough,
982 Code,
983 Underline,
984 Spoiler,
985}
986
987/// Describes formatting on string content
988#[derive(Clone, Debug, Default)]
989pub struct Content {
990 pub italic: bool,
991 pub bold: bool,
992 pub strikethrough: bool,
993 pub inner: String,
994 pub code: bool,
995 pub underline: bool,
996 pub spoiler: bool,
997}
998
999impl<T: AsRef<str>> Add<T> for Content {
1000 type Output = Content;
1001
1002 fn add(mut self, rhs: T) -> Content {
1003 self.inner += rhs.as_ref();
1004
1005 self
1006 }
1007}
1008
1009impl<T: AsRef<str>> Add<T> for ContentModifier {
1010 type Output = Content;
1011
1012 fn add(self, rhs: T) -> Content {
1013 let mut nc = self.to_content();
1014 nc.inner += rhs.as_ref();
1015 nc
1016 }
1017}
1018
1019impl Add<ContentModifier> for Content {
1020 type Output = Content;
1021
1022 fn add(mut self, rhs: ContentModifier) -> Content {
1023 self.apply(&rhs);
1024
1025 self
1026 }
1027}
1028
1029impl Add<ContentModifier> for ContentModifier {
1030 type Output = Content;
1031
1032 fn add(self, rhs: ContentModifier) -> Content {
1033 let mut nc = self.to_content();
1034 nc.apply(&rhs);
1035
1036 nc
1037 }
1038}
1039
1040impl ContentModifier {
1041 fn to_content(&self) -> Content {
1042 let mut nc = Content::default();
1043 nc.apply(self);
1044
1045 nc
1046 }
1047}
1048
1049impl Content {
1050 pub fn apply(&mut self, modifier: &ContentModifier) {
1051 match *modifier {
1052 ContentModifier::Italic => {
1053 self.italic = true;
1054 },
1055 ContentModifier::Bold => {
1056 self.bold = true;
1057 },
1058 ContentModifier::Strikethrough => {
1059 self.strikethrough = true;
1060 },
1061 ContentModifier::Code => {
1062 self.code = true;
1063 },
1064 ContentModifier::Underline => {
1065 self.underline = true;
1066 },
1067 ContentModifier::Spoiler => {
1068 self.spoiler = true;
1069 },
1070 }
1071 }
1072}
1073
1074impl std::fmt::Display for Content {
1075 fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1076 if self.spoiler {
1077 fmt.write_str("||")?;
1078 }
1079
1080 if self.bold {
1081 fmt.write_str("**")?;
1082 }
1083
1084 if self.italic {
1085 fmt.write_char('*')?;
1086 }
1087
1088 if self.strikethrough {
1089 fmt.write_str("~~")?;
1090 }
1091
1092 if self.underline {
1093 fmt.write_str("__")?;
1094 }
1095
1096 if self.code {
1097 fmt.write_char('`')?;
1098 }
1099
1100 fmt.write_str(&self.inner)?;
1101
1102 if self.code {
1103 fmt.write_char('`')?;
1104 }
1105
1106 if self.underline {
1107 fmt.write_str("__")?;
1108 }
1109
1110 if self.strikethrough {
1111 fmt.write_str("~~")?;
1112 }
1113
1114 if self.italic {
1115 fmt.write_char('*')?;
1116 }
1117
1118 if self.bold {
1119 fmt.write_str("**")?;
1120 }
1121
1122 if self.spoiler {
1123 fmt.write_str("||")?;
1124 }
1125
1126 Ok(())
1127 }
1128}
1129
1130impl<T: Into<String>> From<T> for Content {
1131 fn from(t: T) -> Content {
1132 Content {
1133 inner: t.into(),
1134 ..Default::default()
1135 }
1136 }
1137}
1138
1139fn normalize(text: &str) -> String {
1140 // Remove invite links and popular scam websites, mostly to prevent the current user from
1141 // triggering various ad detectors and prevent embeds.
1142 text.replace("discord.gg", "discord\u{2024}gg")
1143 .replace("discord.me", "discord\u{2024}me")
1144 .replace("discordlist.net", "discordlist\u{2024}net")
1145 .replace("discordservers.com", "discordservers\u{2024}com")
1146 .replace("discord.com/invite", "discord\u{2024}com/invite")
1147 .replace("discordapp.com/invite", "discordapp\u{2024}com/invite")
1148 // Remove right-to-left override and other similar annoying symbols
1149 .replace([
1150 '\u{202E}', // RTL Override
1151 '\u{200F}', // RTL Mark
1152 '\u{202B}', // RTL Embedding
1153 '\u{200B}', // Zero-width space
1154 '\u{200D}', // Zero-width joiner
1155 '\u{200C}', // Zero-width non-joiner
1156 ], " ")
1157 // Remove everyone and here mentions. Has to be put after ZWS replacement because it
1158 // utilises it itself.
1159 .replace("@everyone", "@\u{200B}everyone")
1160 .replace("@here", "@\u{200B}here")
1161}
1162
1163#[cfg(test)]
1164mod test {
1165 use super::ContentModifier::{Bold, Code, Italic, Spoiler};
1166 use super::MessageBuilder;
1167 use crate::model::prelude::*;
1168
1169 macro_rules! gen {
1170 ($($fn:ident => [$($text:expr => $expected:expr),+]),+) => ({
1171 $(
1172 $(
1173 assert_eq!(MessageBuilder::new().$fn($text).0, $expected);
1174 )+
1175 )+
1176 });
1177 }
1178
1179 #[test]
1180 fn code_blocks() {
1181 let content = MessageBuilder::new().push_codeblock("test", Some("rb")).build();
1182 assert_eq!(content, "```rb\ntest\n```");
1183 }
1184
1185 #[test]
1186 fn safe_content() {
1187 let content = MessageBuilder::new().push_safe("@everyone discord.gg/discord-api").build();
1188 assert_ne!(content, "@everyone discord.gg/discord-api");
1189 }
1190
1191 #[test]
1192 fn no_free_formatting() {
1193 let content = MessageBuilder::new().push_bold_safe("test**test").build();
1194 assert_ne!(content, "**test**test**");
1195 }
1196
1197 #[test]
1198 fn mentions() {
1199 let content_emoji = MessageBuilder::new()
1200 .emoji(&Emoji {
1201 animated: false,
1202 available: true,
1203 id: EmojiId::new(32),
1204 name: "Rohrkatze".to_string(),
1205 managed: false,
1206 require_colons: true,
1207 roles: vec![],
1208 user: None,
1209 })
1210 .build();
1211 let content_mentions = MessageBuilder::new()
1212 .channel(ChannelId::new(1))
1213 .mention(&UserId::new(2))
1214 .role(RoleId::new(3))
1215 .user(UserId::new(4))
1216 .build();
1217 assert_eq!(content_mentions, "<#1><@2><@&3><@4>");
1218 assert_eq!(content_emoji, "<:Rohrkatze:32>");
1219 }
1220
1221 #[test]
1222 fn content() {
1223 let content = Bold + Italic + Code + "Fun!";
1224
1225 assert_eq!(content.to_string(), "***`Fun!`***");
1226
1227 let content = Spoiler + Bold + "Divert your eyes elsewhere";
1228
1229 assert_eq!(content.to_string(), "||**Divert your eyes elsewhere**||");
1230 }
1231
1232 #[test]
1233 fn init() {
1234 assert_eq!(MessageBuilder::new().0, "");
1235 assert_eq!(MessageBuilder::default().0, "");
1236 }
1237
1238 #[test]
1239 fn message_content() {
1240 let message_content = MessageBuilder::new().push(Bold + Italic + Code + "Fun!").build();
1241
1242 assert_eq!(message_content, "***`Fun!`***");
1243 }
1244
1245 #[test]
1246 fn message_content_safe() {
1247 let message_content = MessageBuilder::new().push_safe(Bold + Italic + "test**test").build();
1248
1249 assert_eq!(message_content, "***test\\*\\*test***");
1250 }
1251
1252 #[test]
1253 fn push() {
1254 assert_eq!(MessageBuilder::new().push('a').0, "a");
1255 assert!(MessageBuilder::new().push("").0.is_empty());
1256 }
1257
1258 #[test]
1259 fn push_codeblock() {
1260 let content = &MessageBuilder::new().push_codeblock("foo", None).0.clone();
1261 assert_eq!(content, "```\nfoo\n```");
1262
1263 let content = &MessageBuilder::new().push_codeblock("fn main() { }", Some("rs")).0.clone();
1264 assert_eq!(content, "```rs\nfn main() { }\n```");
1265 }
1266
1267 #[test]
1268 fn push_codeblock_safe() {
1269 assert_eq!(
1270 MessageBuilder::new().push_codeblock_safe("foo", Some("rs")).0,
1271 "```rs\nfoo\n```",
1272 );
1273 assert_eq!(MessageBuilder::new().push_codeblock_safe("", None).0, "```\n\n```",);
1274 assert_eq!(MessageBuilder::new().push_codeblock_safe("1 * 2", None).0, "```\n1 * 2\n```",);
1275 assert_eq!(
1276 MessageBuilder::new().push_codeblock_safe("`1 * 3`", None).0,
1277 "```\n`1 * 3`\n```",
1278 );
1279 assert_eq!(MessageBuilder::new().push_codeblock_safe("```.```", None).0, "```\n . \n```",);
1280 }
1281
1282 #[test]
1283 fn push_safe() {
1284 gen! {
1285 push_safe => [
1286 "" => "",
1287 "foo" => "foo",
1288 "1 * 2" => "1 \\* 2"
1289 ],
1290 push_bold_safe => [
1291 "" => "****",
1292 "foo" => "**foo**",
1293 "*foo*" => "***foo***",
1294 "f*o**o" => "**f*o o**"
1295 ],
1296 push_italic_safe => [
1297 "" => "__",
1298 "foo" => "_foo_",
1299 "f_o_o" => "_f o o_"
1300 ],
1301 push_mono_safe => [
1302 "" => "``",
1303 "foo" => "`foo`",
1304 "asterisk *" => "`asterisk *`",
1305 "`ticks`" => "`'ticks'`"
1306 ],
1307 push_strike_safe => [
1308 "" => "~~~~",
1309 "foo" => "~~foo~~",
1310 "foo ~" => "~~foo ~~~",
1311 "~~foo" => "~~ foo~~",
1312 "~~fo~~o~~" => "~~ fo o ~~"
1313 ],
1314 push_underline_safe => [
1315 "" => "____",
1316 "foo" => "__foo__",
1317 "foo _" => "__foo ___",
1318 "__foo__ bar" => "__ foo bar__"
1319 ],
1320 push_spoiler_safe => [
1321 "" => "||||",
1322 "foo" => "||foo||",
1323 "foo |" => "||foo |||",
1324 "||foo|| bar" =>"|| foo bar||"
1325 ],
1326 push_line_safe => [
1327 "" => "\n",
1328 "foo" => "foo\n",
1329 "1 * 2" => "1 \\* 2\n"
1330 ],
1331 push_mono_line_safe => [
1332 "" => "``\n",
1333 "a ` b `" => "`a ' b '`\n"
1334 ],
1335 push_italic_line_safe => [
1336 "" => "__\n",
1337 "a * c" => "_a * c_\n"
1338 ],
1339 push_bold_line_safe => [
1340 "" => "****\n",
1341 "a ** d" => "**a d**\n"
1342 ],
1343 push_underline_line_safe => [
1344 "" => "____\n",
1345 "a __ e" => "__a e__\n"
1346 ],
1347 push_strike_line_safe => [
1348 "" => "~~~~\n",
1349 "a ~~ f" => "~~a f~~\n"
1350 ],
1351 push_spoiler_line_safe => [
1352 "" => "||||\n",
1353 "a || f" => "||a f||\n"
1354 ]
1355 };
1356 }
1357
1358 #[test]
1359 fn push_unsafe() {
1360 gen! {
1361 push_bold => [
1362 "a" => "**a**",
1363 "" => "****",
1364 '*' => "*****",
1365 "**" => "******"
1366 ],
1367 push_bold_line => [
1368 "" => "****\n",
1369 "foo" => "**foo**\n"
1370 ],
1371 push_italic => [
1372 "a" => "_a_",
1373 "" => "__",
1374 "_" => "___",
1375 "__" => "____"
1376 ],
1377 push_italic_line => [
1378 "" => "__\n",
1379 "foo" => "_foo_\n",
1380 "_?" => "__?_\n"
1381 ],
1382 push_line => [
1383 "" => "\n",
1384 "foo" => "foo\n",
1385 "\n\n" => "\n\n\n",
1386 "\nfoo\n" => "\nfoo\n\n"
1387 ],
1388 push_mono => [
1389 "a" => "`a`",
1390 "" => "``",
1391 "`" => "```",
1392 "``" => "````"
1393 ],
1394 push_mono_line => [
1395 "" => "``\n",
1396 "foo" => "`foo`\n",
1397 "\n" => "`\n`\n",
1398 "`\n`\n" => "``\n`\n`\n"
1399 ],
1400 push_strike => [
1401 "a" => "~~a~~",
1402 "" => "~~~~",
1403 "~" => "~~~~~",
1404 "~~" => "~~~~~~"
1405 ],
1406 push_strike_line => [
1407 "" => "~~~~\n",
1408 "foo" => "~~foo~~\n"
1409 ],
1410 push_underline => [
1411 "a" => "__a__",
1412 "" => "____",
1413 "_" => "_____",
1414 "__" => "______"
1415 ],
1416 push_underline_line => [
1417 "" => "____\n",
1418 "foo" => "__foo__\n"
1419 ],
1420 push_spoiler => [
1421 "a" => "||a||",
1422 "" => "||||",
1423 "|" => "|||||",
1424 "||" => "||||||"
1425 ],
1426 push_spoiler_line => [
1427 "" => "||||\n",
1428 "foo" => "||foo||\n"
1429 ]
1430 };
1431 }
1432
1433 #[test]
1434 fn normalize() {
1435 assert_eq!(super::normalize("@everyone"), "@\u{200B}everyone");
1436 assert_eq!(super::normalize("@here"), "@\u{200B}here");
1437 assert_eq!(super::normalize("discord.gg"), "discord\u{2024}gg");
1438 assert_eq!(super::normalize("discord.me"), "discord\u{2024}me");
1439 assert_eq!(super::normalize("discordlist.net"), "discordlist\u{2024}net");
1440 assert_eq!(super::normalize("discordservers.com"), "discordservers\u{2024}com");
1441 assert_eq!(super::normalize("discord.com/invite"), "discord\u{2024}com/invite");
1442 assert_eq!(super::normalize("\u{202E}"), " ");
1443 assert_eq!(super::normalize("\u{200F}"), " ");
1444 assert_eq!(super::normalize("\u{202B}"), " ");
1445 assert_eq!(super::normalize("\u{200B}"), " ");
1446 assert_eq!(super::normalize("\u{200D}"), " ");
1447 assert_eq!(super::normalize("\u{200C}"), " ");
1448 }
1449}