#[cfg(feature = "client")]
mod argument_convert;
pub(crate) mod backports;
mod colour;
#[cfg(feature = "cache")]
mod content_safe;
mod custom_message;
mod message_builder;
pub mod token;
#[cfg(feature = "client")]
pub use argument_convert::*;
#[cfg(feature = "cache")]
pub use content_safe::*;
use url::Url;
pub use self::colour::{colours, Colour};
pub use self::custom_message::CustomMessage;
pub use self::message_builder::{Content, ContentModifier, EmbedMessageBuilding, MessageBuilder};
#[doc(inline)]
pub use self::token::{parse as parse_token, validate as validate_token};
pub type Color = Colour;
use std::ffi::OsStr;
use std::fs::File;
use std::io::Read;
use std::path::Path;
use crate::internal::prelude::*;
use crate::model::id::EmojiId;
use crate::model::misc::EmojiIdentifier;
#[cfg(feature = "model")]
pub(crate) fn encode_image(raw: &[u8]) -> String {
let mut encoded = base64::encode(raw);
encoded.insert_str(0, "data:image/png;base64,");
encoded
}
#[must_use]
pub fn parse_invite(code: &str) -> &str {
let code = code.trim_start_matches("http://").trim_start_matches("https://");
let lower = code.to_lowercase();
if lower.starts_with("discord.gg/") {
&code[11..]
} else if lower.starts_with("discord.com/invite/") {
&code[19..]
} else {
code
}
}
#[must_use]
pub fn parse_user_tag(s: &str) -> Option<(&str, u16)> {
let (name, discrim) = s.split_once('#')?;
let discrim = discrim.parse().ok()?;
if discrim > 9999 {
return None;
}
Some((name, discrim))
}
pub fn parse_username(mention: impl AsRef<str>) -> Option<u64> {
let mention = mention.as_ref();
if mention.len() < 4 {
return None;
}
if mention.starts_with("<@!") {
let len = mention.len() - 1;
mention[3..len].parse::<u64>().ok()
} else if mention.starts_with("<@") {
let len = mention.len() - 1;
mention[2..len].parse::<u64>().ok()
} else {
None
}
}
pub fn parse_role(mention: impl AsRef<str>) -> Option<u64> {
let mention = mention.as_ref();
if mention.len() < 4 {
return None;
}
if mention.starts_with("<@&") && mention.ends_with('>') {
let len = mention.len() - 1;
mention[3..len].parse::<u64>().ok()
} else {
None
}
}
pub fn parse_channel(mention: impl AsRef<str>) -> Option<u64> {
let mention = mention.as_ref();
if mention.len() < 4 {
return None;
}
if mention.starts_with("<#") && mention.ends_with('>') {
let len = mention.len() - 1;
mention[2..len].parse::<u64>().ok()
} else {
None
}
}
pub fn parse_emoji(mention: impl AsRef<str>) -> Option<EmojiIdentifier> {
let mention = mention.as_ref();
let len = mention.len();
if !(6..=56).contains(&len) {
return None;
}
if (mention.starts_with("<:") || mention.starts_with("<a:")) && mention.ends_with('>') {
let mut name = String::default();
let mut id = String::default();
let animated = &mention[1..3] == "a:";
let start = if animated { 3 } else { 2 };
for (i, x) in mention[start..].chars().enumerate() {
if x == ':' {
let from = i + start + 1;
for y in mention[from..].chars() {
if y == '>' {
break;
}
id.push(y);
}
break;
}
name.push(x);
}
match id.parse::<u64>() {
Ok(x) => Some(EmojiIdentifier {
animated,
name,
id: EmojiId(x),
}),
_ => None,
}
} else {
None
}
}
#[inline]
pub fn read_image<P: AsRef<Path>>(path: P) -> Result<String> {
_read_image(path.as_ref())
}
fn _read_image(path: &Path) -> Result<String> {
let mut v = Vec::default();
let mut f = File::open(path)?;
drop(f.read_to_end(&mut v));
let b64 = base64::encode(&v);
let ext = if path.extension() == Some(OsStr::new("png")) { "png" } else { "jpg" };
Ok(format!("data:image/{};base64,{}", ext, b64))
}
pub fn parse_quotes(s: impl AsRef<str>) -> Vec<String> {
let s = s.as_ref();
let mut args = vec![];
let mut in_string = false;
let mut escaping = false;
let mut current_str = String::default();
for x in s.chars() {
if in_string {
if x == '\\' && !escaping {
escaping = true;
} else if x == '"' && !escaping {
if !current_str.is_empty() {
args.push(current_str);
}
current_str = String::default();
in_string = false;
} else {
current_str.push(x);
escaping = false;
}
} else if x == ' ' {
if !current_str.is_empty() {
args.push(current_str.clone());
}
current_str = String::default();
} else if x == '"' {
if !current_str.is_empty() {
args.push(current_str.clone());
}
in_string = true;
current_str = String::default();
} else {
current_str.push(x);
}
}
if !current_str.is_empty() {
args.push(current_str);
}
args
}
#[must_use]
pub fn parse_webhook(url: &Url) -> Option<(u64, &str)> {
let (webhook_id, token) = url.path().strip_prefix("/api/webhooks/")?.split_once('/')?;
if !["http", "https"].contains(&url.scheme())
|| !["discord.com", "discordapp.com"].contains(&url.domain()?)
|| !(17..=20).contains(&webhook_id.len())
|| !(60..=68).contains(&token.len())
{
return None;
}
Some((webhook_id.parse().ok()?, token))
}
#[inline]
pub fn shard_id(guild_id: impl Into<u64>, shard_count: u64) -> u64 {
(guild_id.into() >> 22) % shard_count
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_invite_parser() {
assert_eq!(parse_invite("https://discord.gg/abc"), "abc");
assert_eq!(parse_invite("http://discord.gg/abc"), "abc");
assert_eq!(parse_invite("discord.gg/abc"), "abc");
assert_eq!(parse_invite("DISCORD.GG/ABC"), "ABC");
assert_eq!(parse_invite("https://discord.com/invite/abc"), "abc");
assert_eq!(parse_invite("http://discord.com/invite/abc"), "abc");
assert_eq!(parse_invite("discord.com/invite/abc"), "abc");
}
#[test]
fn test_username_parser() {
assert_eq!(parse_username("<@12345>").unwrap(), 12_345);
assert_eq!(parse_username("<@!12345>").unwrap(), 12_345);
}
#[test]
fn role_parser() {
assert_eq!(parse_role("<@&12345>").unwrap(), 12_345);
}
#[test]
fn test_channel_parser() {
assert_eq!(parse_channel("<#12345>").unwrap(), 12_345);
}
#[test]
fn test_emoji_parser() {
let emoji = parse_emoji("<:name:12345>").unwrap();
assert_eq!(emoji.name, "name");
assert_eq!(emoji.id, 12_345);
}
#[test]
fn test_quote_parser() {
let parsed = parse_quotes("a \"b c\" d\"e f\" g");
assert_eq!(parsed, ["a", "b c", "d", "e f", "g"]);
}
#[test]
fn test_webhook_parser() {
let url = "https://discord.com/api/webhooks/245037420704169985/ig5AO-wdVWpCBtUUMxmgsWryqgsW3DChbKYOINftJ4DCrUbnkedoYZD0VOH1QLr-S3sV".parse().unwrap();
let (id, token) = parse_webhook(&url).unwrap();
assert_eq!(id, 245037420704169985);
assert_eq!(token, "ig5AO-wdVWpCBtUUMxmgsWryqgsW3DChbKYOINftJ4DCrUbnkedoYZD0VOH1QLr-S3sV");
}
}