Skip to main content

serenity/gateway/bridge/
shard_messenger.rs

1#[cfg(feature = "collector")]
2use std::sync::Arc;
3
4use futures::channel::mpsc::UnboundedSender as Sender;
5use tokio_tungstenite::tungstenite::Message;
6
7#[cfg(feature = "collector")]
8use super::CollectorCallback;
9use super::{ChunkGuildFilter, ShardRunner, ShardRunnerMessage};
10use crate::gateway::ActivityData;
11use crate::model::prelude::*;
12
13/// A handle to a [`ShardRunner`].
14///
15/// This is used to cleanly communicate with a shard's respective [`ShardRunner`]. This can be used
16/// for actions such as setting the activity via [`Self::set_activity`] or shutting down via
17/// [`Self::shutdown_clean`].
18///
19/// [`ShardRunner`]: super::ShardRunner
20#[derive(Clone, Debug)]
21pub struct ShardMessenger {
22    pub(crate) tx: Sender<ShardRunnerMessage>,
23    #[cfg(feature = "collector")]
24    pub(crate) collectors: Arc<std::sync::Mutex<Vec<CollectorCallback>>>,
25}
26
27impl ShardMessenger {
28    /// Creates a new shard messenger.
29    ///
30    /// If you are using the [`Client`], you do not need to do this.
31    ///
32    /// [`Client`]: crate::Client
33    #[inline]
34    #[must_use]
35    pub fn new(shard: &ShardRunner) -> Self {
36        Self {
37            tx: shard.runner_tx(),
38            #[cfg(feature = "collector")]
39            collectors: Arc::clone(&shard.collectors),
40        }
41    }
42
43    /// Requests that one or multiple [`Guild`]s be chunked.
44    ///
45    /// This will ask the gateway to start sending member chunks for large guilds (250 members+).
46    /// If a guild is over 250 members, then a full member list will not be downloaded, and must
47    /// instead be requested to be sent in "chunks" containing members.
48    ///
49    /// Member chunks are sent as the [`Event::GuildMembersChunk`] event. Each chunk only contains
50    /// a partial amount of the total members.
51    ///
52    /// If the `cache` feature is enabled, the cache will automatically be updated with member
53    /// chunks.
54    ///
55    /// # Examples
56    ///
57    /// Chunk a single guild by Id, limiting to 2000 [`Member`]s, and not specifying a query
58    /// parameter:
59    ///
60    /// ```rust,no_run
61    /// # use tokio::sync::Mutex;
62    /// # use serenity::model::gateway::{GatewayIntents, ShardInfo};
63    /// # use serenity::model::id::ShardId;
64    /// # use serenity::gateway::{ChunkGuildFilter, Shard};
65    /// # use std::sync::Arc;
66    /// #
67    /// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
68    /// #     let mutex = Arc::new(Mutex::new("".to_string()));
69    /// #
70    /// #     let shard_info = ShardInfo {
71    /// #         id: ShardId(0),
72    /// #         total: 1,
73    /// #     };
74    /// #     let mut shard = Shard::new(mutex.clone(), "", shard_info, GatewayIntents::all(), None).await?;
75    /// #
76    /// use serenity::model::id::GuildId;
77    ///
78    /// shard.chunk_guild(GuildId::new(81384788765712384), Some(2000), false, ChunkGuildFilter::None, None);
79    /// # Ok(())
80    /// # }
81    /// ```
82    ///
83    /// Chunk a single guild by Id, limiting to 20 members, specifying a query parameter of `"do"`
84    /// and a nonce of `"request"`:
85    ///
86    /// ```rust,no_run
87    /// # use tokio::sync::Mutex;
88    /// # use serenity::model::gateway::{GatewayIntents, ShardInfo};
89    /// # use serenity::model::id::ShardId;
90    /// # use serenity::gateway::{ChunkGuildFilter, Shard};
91    /// # use std::sync::Arc;
92    /// #
93    /// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
94    /// #     let mutex = Arc::new(Mutex::new("".to_string()));
95    /// #
96    /// #     let shard_info = ShardInfo {
97    /// #         id: ShardId(0),
98    /// #         total: 1,
99    /// #     };
100    /// #
101    /// #     let mut shard = Shard::new(mutex.clone(), "", shard_info, GatewayIntents::all(), None).await?;;
102    /// #
103    /// use serenity::model::id::GuildId;
104    ///
105    /// shard.chunk_guild(
106    ///     GuildId::new(81384788765712384),
107    ///     Some(20),
108    ///     false,
109    ///     ChunkGuildFilter::Query("do".to_owned()),
110    ///     Some("request"),
111    /// );
112    /// # Ok(())
113    /// # }
114    /// ```
115    pub fn chunk_guild(
116        &self,
117        guild_id: GuildId,
118        limit: Option<u16>,
119        presences: bool,
120        filter: ChunkGuildFilter,
121        nonce: Option<String>,
122    ) {
123        self.send_to_shard(ShardRunnerMessage::ChunkGuild {
124            guild_id,
125            limit,
126            presences,
127            filter,
128            nonce,
129        });
130    }
131
132    /// Requests [Soundboard sounds][soundboard] to be fetched from one or multiple [`Guild`]s.
133    ///
134    /// This will ask the gateway to start sending soundboard sounds.
135    ///
136    /// Soundboard sounds are sent as the [`Event::SoundboardSounds`] event.
137    ///
138    /// [`Event::SoundboardSounds`]: crate::model::event::Event::SoundboardSounds
139    /// [`Guild`]: crate::model::guild::Guild
140    /// [soundboard]: crate::model::soundboard::Soundboard
141    pub fn request_soundboard_sounds(&self, guild_ids: Vec<GuildId>) {
142        self.send_to_shard(ShardRunnerMessage::SoundboardSounds {
143            guild_ids,
144        });
145    }
146
147    /// Sets the user's current activity, if any.
148    ///
149    /// Other presence settings are maintained.
150    ///
151    /// # Examples
152    ///
153    /// Setting the current activity to playing `"Heroes of the Storm"`:
154    ///
155    /// ```rust,no_run
156    /// # use tokio::sync::Mutex;
157    /// # use serenity::gateway::{Shard};
158    /// # use serenity::model::id::ShardId;
159    /// # use serenity::model::gateway::{GatewayIntents, ShardInfo};
160    /// # use std::sync::Arc;
161    /// #
162    /// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
163    /// #     let mutex = Arc::new(Mutex::new("".to_string()));
164    /// #
165    /// #     let shard_info = ShardInfo {
166    /// #         id: ShardId(0),
167    /// #         total: 1,
168    /// #     };
169    /// #
170    /// #     let mut shard = Shard::new(mutex.clone(), "", shard_info, GatewayIntents::all(), None).await?;
171    /// use serenity::gateway::ActivityData;
172    ///
173    /// shard.set_activity(Some(ActivityData::playing("Heroes of the Storm")));
174    /// # Ok(())
175    /// # }
176    /// ```
177    pub fn set_activity(&self, activity: Option<ActivityData>) {
178        self.send_to_shard(ShardRunnerMessage::SetActivity(activity));
179    }
180
181    /// Sets the user's full presence information.
182    ///
183    /// Consider using the individual setters if you only need to modify one of these.
184    ///
185    /// # Examples
186    ///
187    /// Set the current user as playing `"Heroes of the Storm"` and being online:
188    ///
189    /// ```rust,ignore
190    /// # use tokio::sync::Mutex;
191    /// # use serenity::gateway::Shard;
192    /// # use std::sync::Arc;
193    /// #
194    /// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
195    /// #     let mutex = Arc::new(Mutex::new("".to_string()));
196    /// #
197    /// #     let shard_info = ShardInfo {
198    /// #         id: 0,
199    /// #         total: 1,
200    /// #     };
201    /// #
202    /// #     let mut shard = Shard::new(mutex.clone(), "", shard_info, None).await?;
203    /// #
204    /// use serenity::gateway::ActivityData;
205    /// use serenity::model::user::OnlineStatus;
206    ///
207    /// let activity = ActivityData::playing("Heroes of the Storm");
208    /// shard.set_presence(Some(activity), OnlineStatus::Online);
209    /// # Ok(())
210    /// # }
211    /// ```
212    pub fn set_presence(&self, activity: Option<ActivityData>, mut status: OnlineStatus) {
213        if status == OnlineStatus::Offline {
214            status = OnlineStatus::Invisible;
215        }
216
217        self.send_to_shard(ShardRunnerMessage::SetPresence(activity, status));
218    }
219
220    /// Sets the user's current online status.
221    ///
222    /// Note that [`Offline`] is not a valid online status, so it is automatically converted to
223    /// [`Invisible`].
224    ///
225    /// Other presence settings are maintained.
226    ///
227    /// # Examples
228    ///
229    /// Setting the current online status for the shard to [`DoNotDisturb`].
230    ///
231    /// ```rust,no_run
232    /// # use tokio::sync::Mutex;
233    /// # use serenity::gateway::{Shard};
234    /// # use serenity::model::id::ShardId;
235    /// # use serenity::model::gateway::{GatewayIntents, ShardInfo};
236    /// # use std::sync::Arc;
237    /// #
238    /// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
239    /// #     let mutex = Arc::new(Mutex::new("".to_string()));
240    /// #     let shard_info = ShardInfo {
241    /// #         id: ShardId(0),
242    /// #         total: 1,
243    /// #     };
244    /// #
245    /// #     let mut shard = Shard::new(mutex.clone(), "", shard_info, GatewayIntents::all(), None).await?;
246    /// #
247    /// use serenity::model::user::OnlineStatus;
248    ///
249    /// shard.set_status(OnlineStatus::DoNotDisturb);
250    /// # Ok(())
251    /// # }
252    /// ```
253    ///
254    /// [`DoNotDisturb`]: OnlineStatus::DoNotDisturb
255    /// [`Invisible`]: OnlineStatus::Invisible
256    /// [`Offline`]: OnlineStatus::Offline
257    pub fn set_status(&self, mut online_status: OnlineStatus) {
258        if online_status == OnlineStatus::Offline {
259            online_status = OnlineStatus::Invisible;
260        }
261
262        self.send_to_shard(ShardRunnerMessage::SetStatus(online_status));
263    }
264
265    /// Shuts down the websocket by attempting to cleanly close the connection.
266    pub fn shutdown_clean(&self) {
267        self.send_to_shard(ShardRunnerMessage::Close(1000, None));
268    }
269
270    /// Sends a raw message over the WebSocket.
271    ///
272    /// The given message is not mutated in any way, and is sent as-is.
273    ///
274    /// You should only use this if you know what you're doing. If you're wanting to, for example,
275    /// send a presence update, prefer the usage of the [`Self::set_presence`] method.
276    pub fn websocket_message(&self, message: Message) {
277        self.send_to_shard(ShardRunnerMessage::Message(message));
278    }
279
280    /// Sends a message to the shard.
281    #[inline]
282    pub fn send_to_shard(&self, msg: ShardRunnerMessage) {
283        if let Err(e) = self.tx.unbounded_send(msg) {
284            tracing::warn!("failed to send ShardRunnerMessage to shard: {}", e);
285        }
286    }
287
288    #[cfg(feature = "collector")]
289    pub fn add_collector(&self, collector: CollectorCallback) {
290        self.collectors.lock().expect("poison").push(collector);
291    }
292}
293
294impl AsRef<ShardMessenger> for ShardMessenger {
295    fn as_ref(&self) -> &ShardMessenger {
296        self
297    }
298}