serenity/utils/argument_convert/mod.rs
1mod member;
2pub use member::*;
3
4mod message;
5pub use message::*;
6
7mod user;
8pub use user::*;
9
10mod channel;
11pub use channel::*;
12
13// From HTTP you can only get PartialGuild; for Guild you need gateway and cache
14#[cfg(feature = "cache")]
15mod guild;
16#[cfg(feature = "cache")]
17pub use guild::*;
18
19mod role;
20pub use role::*;
21
22mod emoji;
23pub use emoji::*;
24
25use super::DOMAINS;
26use crate::model::prelude::*;
27use crate::prelude::*;
28
29/// Parse a value from a string in context of a received message.
30///
31/// This trait is a superset of [`std::str::FromStr`]. The difference is that this trait aims to
32/// support serenity-specific Discord types like [`Member`] or [`Message`].
33///
34/// Trait implementations may do network requests as part of their parsing procedure.
35///
36/// Useful for implementing argument parsing in command frameworks.
37#[async_trait::async_trait]
38pub trait ArgumentConvert: Sized {
39 /// The associated error which can be returned from parsing.
40 type Err;
41
42 /// Parses a string `s` as a command parameter of this type.
43 async fn convert(
44 ctx: impl CacheHttp,
45 guild_id: Option<GuildId>,
46 channel_id: Option<ChannelId>,
47 s: &str,
48 ) -> Result<Self, Self::Err>;
49}
50
51#[async_trait::async_trait]
52impl<T: std::str::FromStr> ArgumentConvert for T {
53 type Err = <T as std::str::FromStr>::Err;
54
55 async fn convert(
56 _: impl CacheHttp,
57 _: Option<GuildId>,
58 _: Option<ChannelId>,
59 s: &str,
60 ) -> Result<Self, Self::Err> {
61 T::from_str(s)
62 }
63}
64
65// The following few parse_XXX methods are in here (parse.rs) because they need to be gated behind
66// the model feature and it's just convenient to put them here for that
67
68/// Retrieves IDs from "{channel ID}-{message ID}" (retrieved by shift-clicking on "Copy ID").
69///
70/// If the string is invalid, None is returned.
71///
72/// # Examples
73/// ```rust
74/// use serenity::model::prelude::*;
75/// use serenity::utils::parse_message_id_pair;
76///
77/// assert_eq!(
78/// parse_message_id_pair("673965002805477386-842482646604972082"),
79/// Some((ChannelId::new(673965002805477386), MessageId::new(842482646604972082))),
80/// );
81/// assert_eq!(
82/// parse_message_id_pair("673965002805477386-842482646604972082-472029906943868929"),
83/// None,
84/// );
85/// ```
86#[must_use]
87pub fn parse_message_id_pair(s: &str) -> Option<(ChannelId, MessageId)> {
88 let mut parts = s.splitn(2, '-');
89 let channel_id = parts.next()?.parse().ok()?;
90 let message_id = parts.next()?.parse().ok()?;
91 Some((channel_id, message_id))
92}
93
94/// Retrieves guild, channel, and message ID from a message URL.
95///
96/// If the URL is malformed, None is returned.
97///
98/// # Examples
99/// ```rust
100/// use serenity::model::prelude::*;
101/// use serenity::utils::parse_message_url;
102///
103/// assert_eq!(
104/// parse_message_url(
105/// "https://discord.com/channels/381880193251409931/381880193700069377/806164913558781963"
106/// ),
107/// Some((
108/// GuildId::new(381880193251409931),
109/// ChannelId::new(381880193700069377),
110/// MessageId::new(806164913558781963),
111/// )),
112/// );
113/// assert_eq!(
114/// parse_message_url(
115/// "https://canary.discord.com/channels/381880193251409931/381880193700069377/806164913558781963"
116/// ),
117/// Some((
118/// GuildId::new(381880193251409931),
119/// ChannelId::new(381880193700069377),
120/// MessageId::new(806164913558781963),
121/// )),
122/// );
123/// assert_eq!(parse_message_url("https://google.com"), None);
124/// ```
125#[must_use]
126pub fn parse_message_url(s: &str) -> Option<(GuildId, ChannelId, MessageId)> {
127 for domain in DOMAINS {
128 if let Some(parts) = s.strip_prefix(&format!("https://{domain}/channels/")) {
129 let mut parts = parts.splitn(3, '/');
130
131 let guild_id = parts.next()?.parse().ok()?;
132 let channel_id = parts.next()?.parse().ok()?;
133 let message_id = parts.next()?.parse().ok()?;
134 return Some((guild_id, channel_id, message_id));
135 }
136 }
137 None
138}