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 /// Sets the user's current activity, if any.
133 ///
134 /// Other presence settings are maintained.
135 ///
136 /// # Examples
137 ///
138 /// Setting the current activity to playing `"Heroes of the Storm"`:
139 ///
140 /// ```rust,no_run
141 /// # use tokio::sync::Mutex;
142 /// # use serenity::gateway::{Shard};
143 /// # use serenity::model::id::ShardId;
144 /// # use serenity::model::gateway::{GatewayIntents, ShardInfo};
145 /// # use std::sync::Arc;
146 /// #
147 /// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
148 /// # let mutex = Arc::new(Mutex::new("".to_string()));
149 /// #
150 /// # let shard_info = ShardInfo {
151 /// # id: ShardId(0),
152 /// # total: 1,
153 /// # };
154 /// #
155 /// # let mut shard = Shard::new(mutex.clone(), "", shard_info, GatewayIntents::all(), None).await?;
156 /// use serenity::gateway::ActivityData;
157 ///
158 /// shard.set_activity(Some(ActivityData::playing("Heroes of the Storm")));
159 /// # Ok(())
160 /// # }
161 /// ```
162 pub fn set_activity(&self, activity: Option<ActivityData>) {
163 self.send_to_shard(ShardRunnerMessage::SetActivity(activity));
164 }
165
166 /// Sets the user's full presence information.
167 ///
168 /// Consider using the individual setters if you only need to modify one of these.
169 ///
170 /// # Examples
171 ///
172 /// Set the current user as playing `"Heroes of the Storm"` and being online:
173 ///
174 /// ```rust,ignore
175 /// # use tokio::sync::Mutex;
176 /// # use serenity::gateway::Shard;
177 /// # use std::sync::Arc;
178 /// #
179 /// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
180 /// # let mutex = Arc::new(Mutex::new("".to_string()));
181 /// #
182 /// # let shard_info = ShardInfo {
183 /// # id: 0,
184 /// # total: 1,
185 /// # };
186 /// #
187 /// # let mut shard = Shard::new(mutex.clone(), "", shard_info, None).await?;
188 /// #
189 /// use serenity::gateway::ActivityData;
190 /// use serenity::model::user::OnlineStatus;
191 ///
192 /// let activity = ActivityData::playing("Heroes of the Storm");
193 /// shard.set_presence(Some(activity), OnlineStatus::Online);
194 /// # Ok(())
195 /// # }
196 /// ```
197 pub fn set_presence(&self, activity: Option<ActivityData>, mut status: OnlineStatus) {
198 if status == OnlineStatus::Offline {
199 status = OnlineStatus::Invisible;
200 }
201
202 self.send_to_shard(ShardRunnerMessage::SetPresence(activity, status));
203 }
204
205 /// Sets the user's current online status.
206 ///
207 /// Note that [`Offline`] is not a valid online status, so it is automatically converted to
208 /// [`Invisible`].
209 ///
210 /// Other presence settings are maintained.
211 ///
212 /// # Examples
213 ///
214 /// Setting the current online status for the shard to [`DoNotDisturb`].
215 ///
216 /// ```rust,no_run
217 /// # use tokio::sync::Mutex;
218 /// # use serenity::gateway::{Shard};
219 /// # use serenity::model::id::ShardId;
220 /// # use serenity::model::gateway::{GatewayIntents, ShardInfo};
221 /// # use std::sync::Arc;
222 /// #
223 /// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
224 /// # let mutex = Arc::new(Mutex::new("".to_string()));
225 /// # let shard_info = ShardInfo {
226 /// # id: ShardId(0),
227 /// # total: 1,
228 /// # };
229 /// #
230 /// # let mut shard = Shard::new(mutex.clone(), "", shard_info, GatewayIntents::all(), None).await?;
231 /// #
232 /// use serenity::model::user::OnlineStatus;
233 ///
234 /// shard.set_status(OnlineStatus::DoNotDisturb);
235 /// # Ok(())
236 /// # }
237 /// ```
238 ///
239 /// [`DoNotDisturb`]: OnlineStatus::DoNotDisturb
240 /// [`Invisible`]: OnlineStatus::Invisible
241 /// [`Offline`]: OnlineStatus::Offline
242 pub fn set_status(&self, mut online_status: OnlineStatus) {
243 if online_status == OnlineStatus::Offline {
244 online_status = OnlineStatus::Invisible;
245 }
246
247 self.send_to_shard(ShardRunnerMessage::SetStatus(online_status));
248 }
249
250 /// Shuts down the websocket by attempting to cleanly close the connection.
251 pub fn shutdown_clean(&self) {
252 self.send_to_shard(ShardRunnerMessage::Close(1000, None));
253 }
254
255 /// Sends a raw message over the WebSocket.
256 ///
257 /// The given message is not mutated in any way, and is sent as-is.
258 ///
259 /// You should only use this if you know what you're doing. If you're wanting to, for example,
260 /// send a presence update, prefer the usage of the [`Self::set_presence`] method.
261 pub fn websocket_message(&self, message: Message) {
262 self.send_to_shard(ShardRunnerMessage::Message(message));
263 }
264
265 /// Sends a message to the shard.
266 #[inline]
267 pub fn send_to_shard(&self, msg: ShardRunnerMessage) {
268 if let Err(e) = self.tx.unbounded_send(msg) {
269 tracing::warn!("failed to send ShardRunnerMessage to shard: {}", e);
270 }
271 }
272
273 #[cfg(feature = "collector")]
274 pub fn add_collector(&self, collector: CollectorCallback) {
275 self.collectors.lock().expect("poison").push(collector);
276 }
277}
278
279impl AsRef<ShardMessenger> for ShardMessenger {
280 fn as_ref(&self) -> &ShardMessenger {
281 self
282 }
283}