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}