Repository: serenity-rs/serenity (language: rust)

Issue:
Add allowed_mentions to `ClientBuilder`
**description:**

Currently, there is no allowed_mentions in the ClientBuilder, so it is necessary to specify allowed_mentions for each message to be sent.
It would be useful if it could be specified like FrameworkOptions in poise.

**impression:**

```rs
let client = Client::builder(token, intents)
    .allowed_mentions(
        CreateAllowedMentions::new()
            .all_users(false)
            .all_roles(false)
            .everyone(false)
            .replied_user(false)
    )
    .await
    .unwrap()
```


Discussion / hints:
I don't think this is something serenity should implement, being an unopinionated library. Why do you need this feature in serenity, when it is provided by poise?
Sorry, I must have misunderstood about serenity and poise.
You are right, there is an allowed_mentions in the poise configuration, so it was not necessary for serenity.
@kangalio May I ask one question?
Is there any other way to reply Embed in an event handler than using `new_message.channel_id.send_message()`?
If use this method, you need to specify allowed_mentions in CreateMessage individually.
There is not, unfortunately
Sorry if this is a necro - but I don't see why this feature is considered opinionated, it's quite important. The only other option when using serenity rather than poise is to specify on every message that you do not want pings. Perhaps defaulting to a set of mentions would be opinionated... but that's not necessary
> but I don't see why this feature is considered opinionated, it's quite important

True, I was sloppy with my wording. When I said opinionated, I really meant something different: serenity tends to map the Discord API rather closely. Global allowed_mentions is a feature Discord does not have in any way, which means serenity would have to emulate it by adding passing down the global allowed mentions into every message send endpoint, which would increase coupling between parts of serenity in a way we usually try to avoid.

All that said, I was a bit shorted-sighted in my initial judgment and you're right that the above is not necessarily a reason to discard the proposal. After all, we also coupled gateway and HTTP in #1772 so users don't have to keep track of their ApplicationId manually even though those two components are separate.

Fix patch:
diff --git a/src/builder/create_interaction_response.rs b/src/builder/create_interaction_response.rs
index b3a137d0eb8..3d29f98df04 100644
--- a/src/builder/create_interaction_response.rs
+++ b/src/builder/create_interaction_response.rs
@@ -151,7 +151,14 @@ impl Builder for CreateInteractionResponse {
             _ => Vec::new(),
         };
 
-        cache_http.http().create_interaction_response(ctx.0, ctx.1, &self, files).await
+        let http = cache_http.http();
+        if let Self::Message(msg) | Self::Defer(msg) | Self::UpdateMessage(msg) = &mut self {
+            if msg.allowed_mentions.is_none() {
+                msg.allowed_mentions.clone_from(&http.default_allowed_mentions);
+            }
+        };
+
+        http.create_interaction_response(ctx.0, ctx.1, &self, files).await
     }
 }
 
diff --git a/src/builder/create_interaction_response_followup.rs b/src/builder/create_interaction_response_followup.rs
index b14f06fccc3..153313ce21d 100644
--- a/src/builder/create_interaction_response_followup.rs
+++ b/src/builder/create_interaction_response_followup.rs
@@ -189,6 +189,10 @@ impl Builder for CreateInteractionResponseFollowup {
         let files = self.attachments.take_files();
 
         let http = cache_http.http();
+        if self.allowed_mentions.is_none() {
+            self.allowed_mentions.clone_from(&http.default_allowed_mentions);
+        }
+
         match ctx.0 {
             Some(id) => http.as_ref().edit_followup_message(ctx.1, id, &self, files).await,
             None => http.as_ref().create_followup_message(ctx.1, &self, files).await,
diff --git a/src/builder/create_message.rs b/src/builder/create_message.rs
index 99649a12fc4..48f406ac6c9 100644
--- a/src/builder/create_message.rs
+++ b/src/builder/create_message.rs
@@ -334,6 +334,9 @@ impl Builder for CreateMessage {
         let http = cache_http.http();
 
         let files = self.attachments.take_files();
+        if self.allowed_mentions.is_none() {
+            self.allowed_mentions.clone_from(&http.default_allowed_mentions);
+        }
 
         #[cfg_attr(not(feature = "cache"), allow(unused_mut))]
         let mut message = http.send_message(channel_id, files, &self).await?;
diff --git a/src/builder/edit_message.rs b/src/builder/edit_message.rs
index 82f88ba113d..93b181cb8ff 100644
--- a/src/builder/edit_message.rs
+++ b/src/builder/edit_message.rs
@@ -267,6 +267,11 @@ impl Builder for EditMessage {
 
         let files = self.attachments.as_mut().map_or(Vec::new(), |a| a.take_files());
 
-        cache_http.http().edit_message(ctx.0, ctx.1, &self, files).await
+        let http = cache_http.http();
+        if self.allowed_mentions.is_none() {
+            self.allowed_mentions.clone_from(&http.default_allowed_mentions);
+        }
+
+        http.edit_message(ctx.0, ctx.1, &self, files).await
     }
 }
diff --git a/src/builder/edit_webhook_message.rs b/src/builder/edit_webhook_message.rs
index ddcb2d051f4..6053dac9248 100644
--- a/src/builder/edit_webhook_message.rs
+++ b/src/builder/edit_webhook_message.rs
@@ -188,9 +188,11 @@ impl Builder for EditWebhookMessage {
 
         let files = self.attachments.as_mut().map_or(Vec::new(), |a| a.take_files());
 
-        cache_http
-            .http()
-            .edit_webhook_message(ctx.0, self.thread_id, ctx.1, ctx.2, &self, files)
-            .await
+        let http = cache_http.http();
+        if self.allowed_mentions.is_none() {
+            self.allowed_mentions.clone_from(&http.default_allowed_mentions);
+        }
+
+        http.edit_webhook_message(ctx.0, self.thread_id, ctx.1, ctx.2, &self, files).await
     }
 }
diff --git a/src/builder/execute_webhook.rs b/src/builder/execute_webhook.rs
index 40d819fb2fb..4265e9c38b8 100644
--- a/src/builder/execute_webhook.rs
+++ b/src/builder/execute_webhook.rs
@@ -362,6 +362,11 @@ impl Builder for ExecuteWebhook {
 
         let files = self.attachments.take_files();
 
-        cache_http.http().execute_webhook(ctx.0, self.thread_id, ctx.1, ctx.2, files, &self).await
+        let http = cache_http.http();
+        if self.allowed_mentions.is_none() {
+            self.allowed_mentions.clone_from(&http.default_allowed_mentions);
+        }
+
+        http.execute_webhook(ctx.0, self.thread_id, ctx.1, ctx.2, files, &self).await
     }
 }
diff --git a/src/http/client.rs b/src/http/client.rs
index 62c3fcc50fe..fba1c3b10fe 100644
--- a/src/http/client.rs
+++ b/src/http/client.rs
@@ -27,7 +27,7 @@ use super::{
     MessagePagination,
     UserPagination,
 };
-use crate::builder::CreateAttachment;
+use crate::builder::{CreateAllowedMentions, CreateAttachment};
 use crate::constants;
 use crate::internal::prelude::*;
 use crate::json::*;
@@ -56,6 +56,7 @@ pub struct HttpBuilder {
     token: SecretString,
     proxy: Option<String>,
     application_id: Option<ApplicationId>,
+    default_allowed_mentions: Option<CreateAllowedMentions>,
 }
 
 impl HttpBuilder {
@@ -69,6 +70,7 @@ impl HttpBuilder {
             token: SecretString::new(parse_token(token)),
             proxy: None,
             application_id: None,
+            default_allowed_mentions: None,
         }
     }
 
@@ -127,6 +129,15 @@ impl HttpBuilder {
         self
     }
 
+    /// Sets the [`CreateAllowedMentions`] used by default for each request that would use it.
+    ///
+    /// This only takes effect if you are calling through the model or builder methods, not directly
+    /// calling [`Http`] methods, as [`Http`] is simply used as a convenient storage for these.
+    pub fn default_allowed_mentions(mut self, allowed_mentions: CreateAllowedMentions) -> Self {
+        self.default_allowed_mentions = Some(allowed_mentions);
+        self
+    }
+
     /// Use the given configuration to build the `Http` client.
     #[must_use]
     pub fn build(self) -> Http {
@@ -148,6 +159,7 @@ impl HttpBuilder {
             proxy: self.proxy,
             token: self.token,
             application_id,
+            default_allowed_mentions: self.default_allowed_mentions,
         }
     }
 }
@@ -186,6 +198,7 @@ pub struct Http {
     pub proxy: Option<String>,
     token: SecretString,
     application_id: AtomicU64,
+    pub default_allowed_mentions: Option<CreateAllowedMentions>,
 }
 
 impl Http {


Test patch: