1manual_braid! {
2 pub struct BadgeSetId;
4 pub struct BadgeSetIdRef;
5}
6impl_extra!(BadgeSetId, BadgeSetIdRef);
7
8manual_braid! {
9 pub struct ChatBadgeId;
11 pub struct ChatBadgeIdRef;
12}
13impl_extra!(ChatBadgeId, ChatBadgeIdRef);
14
15manual_braid! {
16 pub struct EmoteId;
18 pub struct EmoteIdRef;
19}
20impl_extra!(EmoteId, EmoteIdRef);
21
22impl EmoteIdRef {
23 pub fn default_render(&self) -> String {
27 EmoteUrlBuilder {
28 id: self.into(),
29 animation_setting: None,
30 theme_mode: EmoteThemeMode::Light,
31 scale: EmoteScale::Size1_0,
32 template: EMOTE_V2_URL_TEMPLATE.into(),
33 }
34 .render()
35 }
36
37 pub fn url(&self) -> EmoteUrlBuilder<'_> { EmoteUrlBuilder::new(self) }
39}
40
41pub(crate) static EMOTE_V2_URL_TEMPLATE: &str =
43 "https://static-cdn.jtvnw.net/emoticons/v2/{{id}}/{{format}}/{{theme_mode}}/{{scale}}";
44
45#[derive(Debug, Clone, PartialEq, Eq)]
47#[cfg_attr(
48 feature = "serde",
49 derive(serde_derive::Serialize, serde_derive::Deserialize)
50)]
51#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
52pub enum EmoteAnimationSetting {
53 Static,
55 Animated,
57}
58
59impl std::fmt::Display for EmoteAnimationSetting {
60 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61 f.write_str(match self {
62 EmoteAnimationSetting::Static => "static",
63 EmoteAnimationSetting::Animated => "animated",
64 })
65 }
66}
67
68#[derive(Debug, Clone, PartialEq, Eq)]
70#[cfg_attr(
71 feature = "serde",
72 derive(serde_derive::Serialize, serde_derive::Deserialize)
73)]
74#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
75pub enum EmoteThemeMode {
76 Light,
78 Dark,
80}
81
82impl Default for EmoteThemeMode {
83 fn default() -> Self { Self::Light }
84}
85
86impl std::fmt::Display for EmoteThemeMode {
87 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
88 f.write_str(match self {
89 EmoteThemeMode::Light => "light",
90 EmoteThemeMode::Dark => "dark",
91 })
92 }
93}
94
95#[derive(Debug, Clone, PartialEq, Eq)]
97#[cfg_attr(
98 feature = "serde",
99 derive(serde_derive::Serialize, serde_derive::Deserialize)
100)]
101pub enum EmoteScale {
102 #[cfg_attr(feature = "serde", serde(rename = "1.0"))]
104 Size1_0,
105 #[cfg_attr(feature = "serde", serde(rename = "2.0"))]
107 Size2_0,
108 #[cfg_attr(feature = "serde", serde(rename = "3.0"))]
110 Size3_0,
111}
112
113impl Default for EmoteScale {
114 fn default() -> Self { Self::Size1_0 }
115}
116
117impl std::fmt::Display for EmoteScale {
118 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
119 f.write_str(match self {
120 EmoteScale::Size1_0 => "1.0",
121 EmoteScale::Size2_0 => "2.0",
122 EmoteScale::Size3_0 => "3.0",
123 })
124 }
125}
126
127#[derive(Debug, Clone)]
137pub struct EmoteUrlBuilder<'a> {
138 pub(crate) id: std::borrow::Cow<'a, EmoteIdRef>,
139 pub(crate) animation_setting: Option<EmoteAnimationSetting>,
140 pub(crate) theme_mode: EmoteThemeMode,
141 pub(crate) scale: EmoteScale,
142 pub(crate) template: std::borrow::Cow<'a, str>,
143}
144
145impl EmoteUrlBuilder<'_> {
146 pub fn new(id: &EmoteIdRef) -> EmoteUrlBuilder<'_> {
151 EmoteUrlBuilder {
152 id: id.into(),
153 animation_setting: <_>::default(),
154 theme_mode: <_>::default(),
155 scale: <_>::default(),
156 template: EMOTE_V2_URL_TEMPLATE.into(),
157 }
158 }
159
160 pub fn size_1x(mut self) -> Self {
162 self.scale = EmoteScale::Size1_0;
163 self
164 }
165
166 pub fn size_2x(mut self) -> Self {
168 self.scale = EmoteScale::Size2_0;
169 self
170 }
171
172 pub fn size_3x(mut self) -> Self {
174 self.scale = EmoteScale::Size3_0;
175 self
176 }
177
178 pub fn dark_mode(mut self) -> Self {
180 self.theme_mode = EmoteThemeMode::Dark;
181 self
182 }
183
184 pub fn light_mode(mut self) -> Self {
186 self.theme_mode = EmoteThemeMode::Light;
187 self
188 }
189
190 pub fn animation_default(mut self) -> Self {
192 self.animation_setting = None;
193 self
194 }
195
196 pub fn animation_static(mut self) -> Self {
198 self.animation_setting = Some(EmoteAnimationSetting::Static);
199 self
200 }
201
202 pub fn animation_animated(mut self) -> Self {
204 self.animation_setting = Some(EmoteAnimationSetting::Animated);
205 self
206 }
207
208 pub fn render(self) -> String {
210 if self.template != "https://static-cdn.jtvnw.net/emoticons/v2/{{id}}/{{format}}/{{theme_mode}}/{{scale}}" {
211 let custom_template = |builder: &EmoteUrlBuilder| -> Option<String> {
212 let mut template = self.template.clone().into_owned();
213 let emote_id_range = template.find("{{id}}")?;
214 template.replace_range(emote_id_range..emote_id_range+"{{id}}".len(), builder.id.as_str());
215 let format_range = template.find("{{format}}")?;
216 template.replace_range(format_range..format_range+"{{format}}".len(), &builder.animation_setting.as_ref().map(|s| s.to_string()).unwrap_or_else(|| String::from("default")));
217 let theme_mode_range = template.find("{{theme_mode}}")?;
218 template.replace_range(theme_mode_range..theme_mode_range+"{{theme_mode}}".len(), &builder.theme_mode.to_string());
219 let scale_range = template.find("{{scale}}")?;
220 template.replace_range(scale_range..scale_range+"{{scale}}".len(), &builder.scale.to_string());
221 if template.contains("{{") || template.contains("}}") {
222 None
223 } else {
224 Some(template)
225 }
226 };
227 if let Some(template) = custom_template(&self) {
228 return template
229 } else {
230 #[cfg(feature = "tracing")]
231 tracing::warn!(template = %self.template, "emote builder was supplied an invalid or unknown template url, falling back to standard builder");
232 }
233 }
234 format!("https://static-cdn.jtvnw.net/emoticons/v2/{emote_id}/{animation_setting}/{theme_mode}/{scale}",
236 emote_id = self.id,
237 animation_setting = self.animation_setting.as_ref().map(|s| s.to_string()).unwrap_or_else(|| String::from("default")),
238 theme_mode = self.theme_mode,
239 scale = self.scale,
240 )
241 }
242}
243
244manual_braid! {
245 pub struct EmoteSetId;
247 pub struct EmoteSetIdRef;
248}
249impl_extra!(EmoteSetId, EmoteSetIdRef);
250
251#[derive(PartialEq, Eq, Debug, Clone)]
253#[cfg_attr(
254 feature = "serde",
255 derive(serde_derive::Serialize, serde_derive::Deserialize)
256)]
257#[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))]
258#[non_exhaustive]
259pub struct EmoteOccurrence {
260 pub begin: i64,
262 pub end: i64,
264 pub id: EmoteId,
266}
267
268impl std::fmt::Display for EmoteOccurrence {
269 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
270 write!(f, "{}:{}-{}", self.id, self.begin, self.end)
271 }
272}
273
274#[deprecated(since = "0.4.8", note = "Use EmoteOccurrence instead")]
276pub type ResubscriptionEmote = EmoteOccurrence;
277
278#[derive(Clone, Debug, PartialEq, Eq)]
280#[cfg_attr(
281 feature = "serde",
282 derive(serde_derive::Serialize, serde_derive::Deserialize)
283)]
284#[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))]
285#[non_exhaustive]
286pub struct Image {
287 pub url_1x: String,
289 pub url_2x: String,
291 pub url_4x: String,
293}