manual_braid! {
pub struct BadgeSetId;
pub struct BadgeSetIdRef;
}
impl_extra!(BadgeSetId, BadgeSetIdRef);
manual_braid! {
pub struct ChatBadgeId;
pub struct ChatBadgeIdRef;
}
impl_extra!(ChatBadgeId, ChatBadgeIdRef);
manual_braid! {
pub struct EmoteId;
pub struct EmoteIdRef;
}
impl_extra!(EmoteId, EmoteIdRef);
impl EmoteIdRef {
pub fn default_render(&self) -> String {
EmoteUrlBuilder {
id: self.into(),
animation_setting: None,
theme_mode: EmoteThemeMode::Light,
scale: EmoteScale::Size1_0,
template: EMOTE_V2_URL_TEMPLATE.into(),
}
.render()
}
pub fn url(&self) -> EmoteUrlBuilder<'_> { EmoteUrlBuilder::new(self) }
}
pub(crate) static EMOTE_V2_URL_TEMPLATE: &str =
"https://static-cdn.jtvnw.net/emoticons/v2/{{id}}/{{format}}/{{theme_mode}}/{{scale}}";
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(
feature = "serde",
derive(serde_derive::Serialize, serde_derive::Deserialize)
)]
#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
pub enum EmoteAnimationSetting {
Static,
Animated,
}
impl std::fmt::Display for EmoteAnimationSetting {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
EmoteAnimationSetting::Static => "static",
EmoteAnimationSetting::Animated => "animated",
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(
feature = "serde",
derive(serde_derive::Serialize, serde_derive::Deserialize)
)]
#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
pub enum EmoteThemeMode {
Light,
Dark,
}
impl Default for EmoteThemeMode {
fn default() -> Self { Self::Light }
}
impl std::fmt::Display for EmoteThemeMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
EmoteThemeMode::Light => "light",
EmoteThemeMode::Dark => "dark",
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(
feature = "serde",
derive(serde_derive::Serialize, serde_derive::Deserialize)
)]
pub enum EmoteScale {
#[cfg_attr(feature = "serde", serde(rename = "1.0"))]
Size1_0,
#[cfg_attr(feature = "serde", serde(rename = "2.0"))]
Size2_0,
#[cfg_attr(feature = "serde", serde(rename = "3.0"))]
Size3_0,
}
impl Default for EmoteScale {
fn default() -> Self { Self::Size1_0 }
}
impl std::fmt::Display for EmoteScale {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
EmoteScale::Size1_0 => "1.0",
EmoteScale::Size2_0 => "2.0",
EmoteScale::Size3_0 => "3.0",
})
}
}
#[derive(Debug, Clone)]
pub struct EmoteUrlBuilder<'a> {
pub(crate) id: std::borrow::Cow<'a, EmoteIdRef>,
pub(crate) animation_setting: Option<EmoteAnimationSetting>,
pub(crate) theme_mode: EmoteThemeMode,
pub(crate) scale: EmoteScale,
pub(crate) template: std::borrow::Cow<'a, str>,
}
impl EmoteUrlBuilder<'_> {
pub fn new(id: &EmoteIdRef) -> EmoteUrlBuilder<'_> {
EmoteUrlBuilder {
id: id.into(),
animation_setting: <_>::default(),
theme_mode: <_>::default(),
scale: <_>::default(),
template: EMOTE_V2_URL_TEMPLATE.into(),
}
}
pub fn size_1x(mut self) -> Self {
self.scale = EmoteScale::Size1_0;
self
}
pub fn size_2x(mut self) -> Self {
self.scale = EmoteScale::Size2_0;
self
}
pub fn size_3x(mut self) -> Self {
self.scale = EmoteScale::Size3_0;
self
}
pub fn dark_mode(mut self) -> Self {
self.theme_mode = EmoteThemeMode::Dark;
self
}
pub fn light_mode(mut self) -> Self {
self.theme_mode = EmoteThemeMode::Light;
self
}
pub fn animation_default(mut self) -> Self {
self.animation_setting = None;
self
}
pub fn animation_static(mut self) -> Self {
self.animation_setting = Some(EmoteAnimationSetting::Static);
self
}
pub fn animation_animated(mut self) -> Self {
self.animation_setting = Some(EmoteAnimationSetting::Animated);
self
}
pub fn render(self) -> String {
if self.template != "https://static-cdn.jtvnw.net/emoticons/v2/{{id}}/{{format}}/{{theme_mode}}/{{scale}}" {
let custom_template = |builder: &EmoteUrlBuilder| -> Option<String> {
let mut template = self.template.clone().into_owned();
let emote_id_range = template.find("{{id}}")?;
template.replace_range(emote_id_range..emote_id_range+"{{id}}".len(), builder.id.as_str());
let format_range = template.find("{{format}}")?;
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")));
let theme_mode_range = template.find("{{theme_mode}}")?;
template.replace_range(theme_mode_range..theme_mode_range+"{{theme_mode}}".len(), &builder.theme_mode.to_string());
let scale_range = template.find("{{scale}}")?;
template.replace_range(scale_range..scale_range+"{{scale}}".len(), &builder.scale.to_string());
if template.contains("{{") || template.contains("}}") {
None
} else {
Some(template)
}
};
if let Some(template) = custom_template(&self) {
return template
} else {
#[cfg(feature = "tracing")]
tracing::warn!(template = %self.template, "emote builder was supplied an invalid or unknown template url, falling back to standard builder");
}
}
format!("https://static-cdn.jtvnw.net/emoticons/v2/{emote_id}/{animation_setting}/{theme_mode}/{scale}",
emote_id = self.id,
animation_setting = self.animation_setting.as_ref().map(|s| s.to_string()).unwrap_or_else(|| String::from("default")),
theme_mode = self.theme_mode,
scale = self.scale,
)
}
}
manual_braid! {
pub struct EmoteSetId;
pub struct EmoteSetIdRef;
}
impl_extra!(EmoteSetId, EmoteSetIdRef);
#[derive(PartialEq, Eq, Debug, Clone)]
#[cfg_attr(
feature = "serde",
derive(serde_derive::Serialize, serde_derive::Deserialize)
)]
#[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))]
#[non_exhaustive]
pub struct ResubscriptionEmote {
pub begin: i64,
pub end: i64,
pub id: EmoteId,
}
impl std::fmt::Display for ResubscriptionEmote {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}:{}-{}", self.id, self.begin, self.end)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(
feature = "serde",
derive(serde_derive::Serialize, serde_derive::Deserialize)
)]
#[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))]
#[non_exhaustive]
pub struct Image {
pub url_1x: String,
pub url_2x: String,
pub url_4x: String,
}