Skip to main content

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}