Skip to content

Commit 7827e5a

Browse files
authored
Factor out collector methods on models into traits (#3055)
Moves the feature-gated collector methods on `UserId`, `MessageId`, etc. into four traits: - `CollectMessages` - `CollectReactions` - `CollectModalInteractions` - `CollectComponentInteractions` This also moves the quick modal machinery into the collector module and defines a `QuickModal` trait. This fully removes any collector feature gates from the model types.
1 parent fbeda94 commit 7827e5a

File tree

14 files changed

+76
-230
lines changed

14 files changed

+76
-230
lines changed

examples/e05_sample_bot_structure/src/commands/modal.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use serenity::builder::*;
2+
use serenity::collector::{CreateQuickModal, QuickModal};
23
use serenity::model::prelude::*;
34
use serenity::prelude::*;
4-
use serenity::utils::CreateQuickModal;
55

66
pub async fn run(ctx: &Context, interaction: &CommandInteraction) -> Result<(), serenity::Error> {
77
let modal = CreateQuickModal::new("About you")

examples/e09_collectors/src/main.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::collections::HashSet;
44
use std::time::Duration;
55

66
use serenity::async_trait;
7-
use serenity::collector::MessageCollector;
7+
use serenity::collector::{CollectMessages, CollectReactions, MessageCollector};
88
// Collectors are streams, that means we can use `StreamExt` and `TryStreamExt`.
99
use serenity::futures::stream::StreamExt;
1010
use serenity::model::prelude::*;
@@ -27,7 +27,7 @@ impl EventHandler for Handler {
2727
// return a builder that can be turned into a Stream, or here, where we can await a
2828
// single reply
2929
let collector =
30-
msg.author.id.await_reply(ctx.shard.clone()).timeout(Duration::from_secs(10));
30+
msg.author.id.collect_messages(ctx.shard.clone()).timeout(Duration::from_secs(10));
3131
if let Some(answer) = collector.await {
3232
if answer.content.to_lowercase() == "ferris" {
3333
let _ = answer.reply(&ctx.http, "That's correct!").await;
@@ -47,7 +47,7 @@ impl EventHandler for Handler {
4747
// The message model can also be turned into a Collector to collect reactions on it.
4848
let collector = react_msg
4949
.id
50-
.await_reaction(ctx.shard.clone())
50+
.collect_reactions(ctx.shard.clone())
5151
.timeout(Duration::from_secs(10))
5252
.author_id(msg.author.id);
5353

examples/e14_message_components/src/main.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use serenity::builder::{
1212
CreateSelectMenuKind,
1313
CreateSelectMenuOption,
1414
};
15+
use serenity::collector::CollectComponentInteractions;
1516
use serenity::futures::StreamExt;
1617
use serenity::model::prelude::*;
1718
use serenity::prelude::*;
@@ -59,7 +60,7 @@ impl EventHandler for Handler {
5960
// manually in the EventHandler.
6061
let interaction = match m
6162
.id
62-
.await_component_interaction(ctx.shard.clone())
63+
.collect_component_interactions(ctx.shard.clone())
6364
.timeout(Duration::from_secs(60 * 3))
6465
.await
6566
{
@@ -107,7 +108,7 @@ impl EventHandler for Handler {
107108

108109
// Wait for multiple interactions
109110
let mut interaction_stream =
110-
m.id.await_component_interaction(ctx.shard.clone())
111+
m.id.collect_component_interactions(ctx.shard.clone())
111112
.timeout(Duration::from_secs(60 * 3))
112113
.stream();
113114

examples/testing/src/main.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::borrow::Cow;
22

33
use serenity::builder::*;
4+
use serenity::collector::CollectComponentInteractions;
45
use serenity::model::prelude::*;
56
use serenity::prelude::*;
67

@@ -121,7 +122,7 @@ async fn message(ctx: &Context, msg: Message) -> Result<(), serenity::Error> {
121122
.await?;
122123
let button_press = msg
123124
.id
124-
.await_component_interaction(ctx.shard.clone())
125+
.collect_component_interactions(ctx.shard.clone())
125126
.timeout(std::time::Duration::from_secs(10))
126127
.await;
127128
match button_press {

src/builder/edit_message.rs

Lines changed: 14 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -107,38 +107,22 @@ impl<'a> EditMessage<'a> {
107107
/// Suppress or unsuppress embeds in the message, this includes those generated by Discord
108108
/// themselves.
109109
///
110-
/// If this is sent directly after posting the message, there is a small chance Discord hasn't
111-
/// yet fully parsed the contained links and generated the embeds, so this embed suppression
112-
/// request has no effect. To mitigate this, you can defer the embed suppression until the
113-
/// embeds have loaded:
114-
///
115-
/// ```rust,no_run
116-
/// # use serenity::all::*;
117-
/// # #[cfg(feature = "collector")]
118-
/// # async fn test(ctx: &Context, channel_id: ChannelId) -> Result<(), Error> {
119-
/// use std::time::Duration;
120-
///
121-
/// use futures::StreamExt;
122-
///
123-
/// let mut msg = channel_id.say(&ctx.http, "<link that spawns an embed>").await?;
124-
///
125-
/// // When the embed appears, a MessageUpdate event is sent and we suppress the embed.
126-
/// // No MessageUpdate event is sent if the message contains no embeddable link or if the link
127-
/// // has been posted before and is still cached in Discord's servers (in which case the
128-
/// // embed appears immediately), no MessageUpdate event is sent. To not wait forever in those
129-
/// // cases, a timeout of 2000ms was added.
130-
/// let msg_id = msg.id;
131-
/// let mut message_updates = serenity::collector::collect(&ctx.shard, move |ev| match ev {
132-
/// Event::MessageUpdate(x) if x.id == msg_id => Some(()),
133-
/// _ => None,
134-
/// });
135-
/// let _ = tokio::time::timeout(Duration::from_millis(2000), message_updates.next()).await;
136-
/// msg.edit(&ctx, EditMessage::new().suppress_embeds(true)).await?;
137-
/// # Ok(()) }
138-
/// ```
110+
/// If this is sent directly after the message has been posted, there is a small chance Discord
111+
/// hasn't yet fully parsed the contained links and generated the embeds, so this embed
112+
/// suppression request has no effect. Note that this is less likely for messages you have not
113+
/// created yourself. There are two ways to mitigate this:
114+
/// 1. If you are editing a message you created, simply set the
115+
/// [`MessageFlags::SUPPRESS_EMBEDS`] flag on creation using [`CreateMessage::flags`] to
116+
/// avoid having to edit the message in the first place.
117+
/// 2. Defer the embed suppression until the embed has loaded. When the embed appears, a
118+
/// `MessageUpdate` event is sent over the gateway. Note that this will not occur if a link
119+
/// has been previously posted and is still cached by Discord, in which case the embed will
120+
/// immediately appear.
121+
///
122+
/// [`CreateMessage::flags`]: super::CreateMessage::flags
139123
pub fn suppress_embeds(mut self, suppress: bool) -> Self {
140124
// At time of writing, only `SUPPRESS_EMBEDS` can be set/unset when editing messages. See
141-
// for details: https://discord.com/developers/docs/resources/channel#edit-message-jsonform-params
125+
// for details: https://discord.com/developers/docs/resources/message#edit-message-jsonform-params
142126
let flags =
143127
suppress.then_some(MessageFlags::SUPPRESS_EMBEDS).unwrap_or_else(MessageFlags::empty);
144128

src/collector.rs renamed to src/collector/mod.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
mod quick_modal;
2+
13
use std::sync::Arc;
24

35
use futures::future::pending;
46
use futures::{Stream, StreamExt as _};
7+
pub use quick_modal::*;
58

69
use crate::gateway::{CollectorCallback, ShardMessenger};
710
use crate::internal::prelude::*;
@@ -51,6 +54,7 @@ macro_rules! make_specific_collector {
5154
(
5255
$( #[ $($meta:tt)* ] )*
5356
$collector_type:ident, $item_type:ident,
57+
$collector_trait:ident, $method_name:ident,
5458
$extractor:pat => $extracted_item:ident,
5559
$( $filter_name:ident: $filter_type:ty => $filter_passes:expr, )*
5660
) => {
@@ -100,7 +104,7 @@ macro_rules! make_specific_collector {
100104
let filters_pass = move |$extracted_item: &$item_type| {
101105
// Check each of the built-in filters (author_id, channel_id, etc.)
102106
$( if let Some($filter_name) = &self.$filter_name {
103-
if !$filter_passes {
107+
if !($filter_passes) {
104108
return false;
105109
}
106110
} )*
@@ -142,12 +146,27 @@ macro_rules! make_specific_collector {
142146
Box::pin(self.next())
143147
}
144148
}
149+
150+
pub trait $collector_trait {
151+
fn $method_name(self, shard_messenger: ShardMessenger) -> $collector_type;
152+
}
153+
154+
$(
155+
impl $collector_trait for $filter_type {
156+
fn $method_name(self, shard_messenger: ShardMessenger) -> $collector_type {
157+
$collector_type::new(shard_messenger).$filter_name(self)
158+
}
159+
}
160+
)*
145161
};
146162
}
147163

148164
make_specific_collector!(
149165
// First line has name of the collector type, and the type of the collected items.
150166
ComponentInteractionCollector, ComponentInteraction,
167+
// Second line has name of the specific trait and method name that will be
168+
// implemented on the filter argument types listed below.
169+
CollectComponentInteractions, collect_component_interactions,
151170
// This defines the extractor pattern, which extracts the data we want to collect from an Event.
152171
Event::InteractionCreate(InteractionCreateEvent {
153172
interaction: Interaction::Component(interaction),
@@ -165,6 +184,7 @@ make_specific_collector!(
165184
);
166185
make_specific_collector!(
167186
ModalInteractionCollector, ModalInteraction,
187+
CollectModalInteractions, collect_modal_interactions,
168188
Event::InteractionCreate(InteractionCreateEvent {
169189
interaction: Interaction::Modal(interaction),
170190
}) => interaction,
@@ -176,6 +196,7 @@ make_specific_collector!(
176196
);
177197
make_specific_collector!(
178198
ReactionCollector, Reaction,
199+
CollectReactions, collect_reactions,
179200
Event::ReactionAdd(ReactionAddEvent { reaction }) => reaction,
180201
author_id: UserId => reaction.user_id.map_or(true, |a| a == *author_id),
181202
channel_id: ChannelId => reaction.channel_id == *channel_id,
@@ -184,6 +205,7 @@ make_specific_collector!(
184205
);
185206
make_specific_collector!(
186207
MessageCollector, Message,
208+
CollectMessages, collect_messages,
187209
Event::MessageCreate(MessageCreateEvent { message }) => message,
188210
author_id: UserId => message.author.id == *author_id,
189211
channel_id: ChannelId => message.channel_id == *channel_id,

src/utils/quick_modal.rs renamed to src/collector/quick_modal.rs

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
use std::borrow::Cow;
2+
use std::future::Future;
23

34
use crate::builder::{CreateActionRow, CreateInputText, CreateInteractionResponse, CreateModal};
45
use crate::collector::ModalInteractionCollector;
56
use crate::gateway::client::Context;
67
use crate::internal::prelude::*;
78
use crate::model::prelude::*;
89

9-
#[cfg(feature = "collector")]
1010
pub struct QuickModalResponse {
1111
pub interaction: ModalInteraction,
1212
pub inputs: FixedArray<FixedString<u16>>,
@@ -15,7 +15,7 @@ pub struct QuickModalResponse {
1515
/// Convenience builder to create a modal, wait for the user to submit and parse the response.
1616
///
1717
/// ```rust
18-
/// # use serenity::{builder::*, model::prelude::*, prelude::*, utils::CreateQuickModal, Result};
18+
/// # use serenity::{builder::*, model::prelude::*, prelude::*, collector::*, Result};
1919
/// # async fn foo_(ctx: &Context, interaction: &CommandInteraction) -> Result<()> {
2020
/// let modal = CreateQuickModal::new("About you")
2121
/// .timeout(std::time::Duration::from_secs(600))
@@ -28,15 +28,13 @@ pub struct QuickModalResponse {
2828
/// # Ok(())
2929
/// # }
3030
/// ```
31-
#[cfg(feature = "collector")]
3231
#[must_use]
3332
pub struct CreateQuickModal<'a> {
3433
title: Cow<'a, str>,
3534
timeout: Option<std::time::Duration>,
3635
input_texts: Vec<CreateInputText<'a>>,
3736
}
3837

39-
#[cfg(feature = "collector")]
4038
impl<'a> CreateQuickModal<'a> {
4139
pub fn new(title: impl Into<Cow<'a, str>>) -> Self {
4240
Self {
@@ -143,3 +141,31 @@ impl<'a> CreateQuickModal<'a> {
143141
}))
144142
}
145143
}
144+
145+
pub trait QuickModal {
146+
fn quick_modal(
147+
&self,
148+
ctx: &Context,
149+
builder: CreateQuickModal<'_>,
150+
) -> impl Future<Output = Result<Option<QuickModalResponse>>>;
151+
}
152+
153+
impl QuickModal for CommandInteraction {
154+
async fn quick_modal(
155+
&self,
156+
ctx: &Context,
157+
builder: CreateQuickModal<'_>,
158+
) -> Result<Option<QuickModalResponse>> {
159+
builder.execute(ctx, self.id, &self.token).await
160+
}
161+
}
162+
163+
impl QuickModal for ComponentInteraction {
164+
async fn quick_modal(
165+
&self,
166+
ctx: &Context,
167+
builder: CreateQuickModal<'_>,
168+
) -> Result<Option<QuickModalResponse>> {
169+
builder.execute(ctx, self.id, &self.token).await
170+
}
171+
}

src/model/application/command_interaction.rs

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,10 @@ use crate::builder::{
1212
CreateInteractionResponseMessage,
1313
EditInteractionResponse,
1414
};
15-
#[cfg(feature = "collector")]
16-
use crate::gateway::client::Context;
1715
#[cfg(feature = "model")]
1816
use crate::http::Http;
1917
use crate::internal::utils::lending_for_each;
2018
use crate::model::prelude::*;
21-
#[cfg(all(feature = "collector", feature = "utils"))]
22-
use crate::utils::{CreateQuickModal, QuickModalResponse};
2319

2420
/// An interaction when a user invokes a slash command.
2521
///
@@ -204,20 +200,6 @@ impl CommandInteraction {
204200
);
205201
self.create_response(http, builder).await
206202
}
207-
208-
/// See [`CreateQuickModal`].
209-
///
210-
/// # Errors
211-
///
212-
/// See [`CreateQuickModal::execute()`].
213-
#[cfg(all(feature = "collector", feature = "utils"))]
214-
pub async fn quick_modal(
215-
&self,
216-
ctx: &Context,
217-
builder: CreateQuickModal<'_>,
218-
) -> Result<Option<QuickModalResponse>> {
219-
builder.execute(ctx, self.id, &self.token).await
220-
}
221203
}
222204

223205
// Manual impl needed to insert guild_id into resolved Role's

src/model/application/component_interaction.rs

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,9 @@ use crate::builder::{
99
CreateInteractionResponseMessage,
1010
EditInteractionResponse,
1111
};
12-
#[cfg(feature = "collector")]
13-
use crate::gateway::client::Context;
1412
#[cfg(feature = "model")]
1513
use crate::http::Http;
1614
use crate::model::prelude::*;
17-
#[cfg(all(feature = "collector", feature = "utils"))]
18-
use crate::utils::{CreateQuickModal, QuickModalResponse};
1915

2016
/// An interaction triggered by a message component.
2117
///
@@ -201,20 +197,6 @@ impl ComponentInteraction {
201197
);
202198
self.create_response(http, builder).await
203199
}
204-
205-
/// See [`CreateQuickModal`].
206-
///
207-
/// # Errors
208-
///
209-
/// See [`CreateQuickModal::execute()`].
210-
#[cfg(all(feature = "collector", feature = "utils"))]
211-
pub async fn quick_modal(
212-
&self,
213-
ctx: &Context,
214-
builder: CreateQuickModal<'_>,
215-
) -> Result<Option<QuickModalResponse>> {
216-
builder.execute(ctx, self.id, &self.token).await
217-
}
218200
}
219201

220202
// Manual impl needed to insert guild_id into model data

src/model/channel/channel_id.rs

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,6 @@ use crate::builder::{
2323
};
2424
#[cfg(all(feature = "cache", feature = "model"))]
2525
use crate::cache::Cache;
26-
#[cfg(feature = "collector")]
27-
use crate::collector::{MessageCollector, ReactionCollector};
28-
#[cfg(feature = "collector")]
29-
use crate::gateway::ShardMessenger;
3026
#[cfg(feature = "model")]
3127
use crate::http::{CacheHttp, Http, Typing};
3228
use crate::model::prelude::*;
@@ -802,32 +798,6 @@ impl ChannelId {
802798
builder.execute(http, self).await
803799
}
804800

805-
/// Returns a builder which can be awaited to obtain a message or stream of messages in this
806-
/// channel.
807-
#[cfg(feature = "collector")]
808-
pub fn await_reply(self, shard_messenger: ShardMessenger) -> MessageCollector {
809-
MessageCollector::new(shard_messenger).channel_id(self)
810-
}
811-
812-
/// Same as [`Self::await_reply`].
813-
#[cfg(feature = "collector")]
814-
pub fn await_replies(self, shard_messenger: ShardMessenger) -> MessageCollector {
815-
self.await_reply(shard_messenger)
816-
}
817-
818-
/// Returns a builder which can be awaited to obtain a reaction or stream of reactions sent in
819-
/// this channel.
820-
#[cfg(feature = "collector")]
821-
pub fn await_reaction(self, shard_messenger: ShardMessenger) -> ReactionCollector {
822-
ReactionCollector::new(shard_messenger).channel_id(self)
823-
}
824-
825-
/// Same as [`Self::await_reaction`].
826-
#[cfg(feature = "collector")]
827-
pub fn await_reactions(self, shard_messenger: ShardMessenger) -> ReactionCollector {
828-
self.await_reaction(shard_messenger)
829-
}
830-
831801
/// Gets a stage instance.
832802
///
833803
/// # Errors

0 commit comments

Comments
 (0)