1#[cfg(feature = "discord")]
2use regex::Regex;
3use rustwemoji::get;
4
5#[cfg(feature = "discord")]
6const RE_DISCORD_EMOJI: &str = r"<a?:[a-zA-Z0-9_]+:([0-9]{17,19})>";
7
8#[derive(Debug, PartialEq, Eq)]
10pub enum Token {
11 Text(String),
13 Emoji(Vec<u8>),
15 #[cfg(feature = "discord")]
16 CustomEmoji(String),
18}
19
20impl Token {
21 pub fn new_text(s: impl Into<String>) -> Self {
22 Self::Text(s.into())
23 }
24 pub fn new_emoji(s: impl Into<Vec<u8>>) -> Self {
25 Self::Emoji(s.into())
26 }
27 #[cfg(feature = "discord")]
28 pub fn new_custom_emoji(s: String) -> Self {
29 let s = format!("https://cdn.discordapp.com/emojis/{}.png?size=96", s);
30 Self::CustomEmoji(s)
31 }
32}
33
34#[cfg(not(feature = "discord"))]
35fn raw_parse(s: String) -> Vec<Token> {
36 raw_parse_emoji(s)
37}
38
39#[cfg(not(feature = "async"))]
40pub fn parse(s: String) -> Vec<Token> {
41 raw_parse(s)
42}
43
44#[cfg(feature = "tokio")]
45pub async fn parse(s: String) -> Result<Vec<Token>, tokio::task::JoinError> {
47 tokio::task::spawn(async { raw_parse(s) }).await
48}
49
50#[cfg(feature = "async-std")]
51pub async fn parse(s: String) -> Vec<Token> {
53 async_std::task::spawn(async { raw_parse(s) }).await
54}
55
56fn raw_parse_emoji(s: String) -> Vec<Token> {
57 let s = s.chars();
58 s.map(|f| f.to_string())
59 .map(|f| {
60 if let Some(v) = get(&f) {
61 Token::new_emoji(v)
62 } else {
63 Token::new_text(f)
64 }
65 })
66 .collect::<Vec<_>>()
67}
68
69#[cfg(feature = "discord")]
70fn raw_parse(s: String) -> Vec<Token> {
71 let mut tokens = Vec::new();
72 let re = Regex::new(RE_DISCORD_EMOJI).unwrap();
73 let mut last = 0;
74 for m in re.find_iter(&s) {
75 let (start, end) = (m.range().start, m.range().end);
76 let text = s[last..start].to_string();
77 let emoji = s[start..end].to_string();
78 let id = re
79 .captures(&emoji)
80 .unwrap()
81 .get(1)
82 .unwrap()
83 .as_str()
84 .to_string();
85 tokens.extend(raw_parse_emoji(text));
86 tokens.push(Token::new_custom_emoji(id));
87 last = end;
88 }
89 let text = s[last..].to_string();
90 tokens.extend(raw_parse_emoji(text));
91 tokens
92}
93
94#[cfg(test)]
95mod test {
96 #[cfg(all(feature = "discord", feature = "async"))]
97 use super::*;
98 #[cfg(all(feature = "discord", feature = "async-std"))]
99 #[async_std::test]
100 async fn test_parse() {
101 let s = "Hello <a:pepega:123456789012345678> World".to_string();
102 let tokens = parse(s).await;
103 assert_eq!(
104 tokens,
105 vec![
106 Token::new_text("H"),
107 Token::new_text("e"),
108 Token::new_text("l"),
109 Token::new_text("l"),
110 Token::new_text("o"),
111 Token::new_text(" "),
112 Token::new_custom_emoji("123456789012345678".to_string()),
113 Token::new_text(" "),
114 Token::new_text("W"),
115 Token::new_text("o"),
116 Token::new_text("r"),
117 Token::new_text("l"),
118 Token::new_text("d"),
119 ]
120 );
121 }
122 #[cfg(all(feature = "discord", feature = "tokio"))]
123 #[tokio::test]
124 async fn test_parse() {
125 let s = "Hello <a:pepega:123456789012345678> World".to_string();
126 let tokens = parse(s).await.unwrap();
127 assert_eq!(
128 tokens,
129 vec![
130 Token::new_text("H"),
131 Token::new_text("e"),
132 Token::new_text("l"),
133 Token::new_text("l"),
134 Token::new_text("o"),
135 Token::new_text(" "),
136 Token::new_custom_emoji("123456789012345678".to_string()),
137 Token::new_text(" "),
138 Token::new_text("W"),
139 Token::new_text("o"),
140 Token::new_text("r"),
141 Token::new_text("l"),
142 Token::new_text("d"),
143 ]
144 );
145 }
146}
147
148#[cfg(all(feature = "async-std", feature = "tokio"))]
150compile_error!("You can only enable one of the async features");