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}