Skip to main content

barbed_core/
emotes.rs

1use bitflags::bitflags;
2use serde::{Deserialize, Serialize};
3
4#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
5pub enum EmoteProvider {
6    Twitch,
7    BetterTtv,
8    SevenTv,
9    FrankerFaceZ,
10    Other(String),
11}
12
13#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
14pub struct EmoteId {
15    pub provider: EmoteProvider,
16    pub value: String,
17}
18
19impl EmoteId {
20    pub fn new(provider: EmoteProvider, value: impl Into<String>) -> Self {
21        Self {
22            provider,
23            value: value.into(),
24        }
25    }
26}
27
28#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
29pub enum EmoteImageFormat {
30    Static,
31    Animated,
32    Other(String),
33}
34
35impl EmoteImageFormat {
36    pub fn parse(value: &str) -> Self {
37        match value {
38            "static" => Self::Static,
39            "animated" => Self::Animated,
40            other => Self::Other(other.to_string()),
41        }
42    }
43
44    pub fn as_str(&self) -> &str {
45        match self {
46            Self::Static => "static",
47            Self::Animated => "animated",
48            Self::Other(value) => value.as_str(),
49        }
50    }
51}
52
53#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
54pub enum EmoteThemeMode {
55    Light,
56    Dark,
57    Other(String),
58}
59
60impl EmoteThemeMode {
61    pub fn parse(value: &str) -> Self {
62        match value {
63            "light" => Self::Light,
64            "dark" => Self::Dark,
65            other => Self::Other(other.to_string()),
66        }
67    }
68
69    pub fn as_str(&self) -> &str {
70        match self {
71            Self::Light => "light",
72            Self::Dark => "dark",
73            Self::Other(value) => value.as_str(),
74        }
75    }
76}
77
78#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
79pub enum EmoteImageScale {
80    One,
81    Two,
82    Three,
83    Other(String),
84}
85
86impl EmoteImageScale {
87    pub fn parse(value: &str) -> Self {
88        match value {
89            "1" | "1.0" => Self::One,
90            "2" | "2.0" => Self::Two,
91            "3" | "3.0" => Self::Three,
92            other => Self::Other(other.to_string()),
93        }
94    }
95
96    pub fn as_str(&self) -> &str {
97        match self {
98            Self::One => "1.0",
99            Self::Two => "2.0",
100            Self::Three => "3.0",
101            Self::Other(value) => value.as_str(),
102        }
103    }
104}
105
106#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
107pub struct EmoteImage {
108    pub format: EmoteImageFormat,
109    pub theme_mode: EmoteThemeMode,
110    pub scale: EmoteImageScale,
111    pub url: String,
112}
113
114bitflags! {
115    #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
116    pub struct EmoteModifierFlags: u32 {
117        const HIDDEN = 1 << 0;
118        const FLIP_X = 1 << 1;
119        const FLIP_Y = 1 << 2;
120        const GROW_X = 1 << 3;
121        const RAINBOW = 1 << 11;
122        const HYPER_RED = 1 << 12;
123        const HYPER_SHAKE = 1 << 13;
124        const CURSED = 1 << 14;
125        const JAM = 1 << 15;
126        const BOUNCE = 1 << 16;
127    }
128}
129
130#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
131pub struct EmoteModifier {
132    pub flags: EmoteModifierFlags,
133    pub raw_flags: u32,
134    pub is_hidden: bool,
135    pub mask_images: Vec<EmoteImage>,
136}
137
138impl EmoteModifier {
139    pub fn has_flag(&self, flag: EmoteModifierFlags) -> bool {
140        self.flags.contains(flag)
141    }
142}
143
144#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
145pub struct Emote {
146    pub id: EmoteId,
147    pub code: String,
148    pub is_animated: bool,
149    pub images: Vec<EmoteImage>,
150    pub modifier: Option<EmoteModifier>,
151}
152
153impl Emote {
154    pub fn new(
155        id: EmoteId,
156        code: impl Into<String>,
157        is_animated: bool,
158        images: Vec<EmoteImage>,
159    ) -> Self {
160        Self {
161            id,
162            code: code.into(),
163            is_animated,
164            images,
165            modifier: None,
166        }
167    }
168
169    pub fn with_modifier(mut self, modifier: EmoteModifier) -> Self {
170        self.modifier = Some(modifier);
171        self
172    }
173
174    pub fn is_modifier(&self) -> bool {
175        self.modifier.is_some()
176    }
177
178    pub fn modifier(&self) -> Option<&EmoteModifier> {
179        self.modifier.as_ref()
180    }
181}
182
183#[cfg(test)]
184mod tests {
185    use super::*;
186
187    #[test]
188    fn image_format_parse_round_trips_known_values() {
189        assert_eq!(EmoteImageFormat::parse("static").as_str(), "static");
190        assert_eq!(EmoteImageFormat::parse("animated").as_str(), "animated");
191    }
192
193    #[test]
194    fn image_scale_parses_provider_variants() {
195        assert_eq!(EmoteImageScale::parse("1"), EmoteImageScale::One);
196        assert_eq!(EmoteImageScale::parse("2.0"), EmoteImageScale::Two);
197        assert_eq!(EmoteImageScale::parse("3"), EmoteImageScale::Three);
198    }
199
200    #[test]
201    fn emote_modifier_accessors_report_presence() {
202        let emote = Emote::new(
203            EmoteId::new(EmoteProvider::SevenTv, "123"),
204            "Wave",
205            false,
206            Vec::new(),
207        )
208        .with_modifier(EmoteModifier {
209            flags: EmoteModifierFlags::RAINBOW,
210            raw_flags: EmoteModifierFlags::RAINBOW.bits(),
211            is_hidden: false,
212            mask_images: Vec::new(),
213        });
214
215        assert!(emote.is_modifier());
216        assert!(
217            emote
218                .modifier()
219                .expect("modifier should exist")
220                .has_flag(EmoteModifierFlags::RAINBOW)
221        );
222    }
223}