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