#![doc(alias = "points")]
#![doc(alias = "channel-points-channel-v1")]
use crate::{pubsub, types};
use serde_derive::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(into = "String", try_from = "String")]
pub struct ChannelPointsChannelV1 {
pub channel_id: u32,
}
impl_de_ser!(
ChannelPointsChannelV1,
"channel-points-channel-v1",
channel_id );
impl pubsub::Topic for ChannelPointsChannelV1 {
#[cfg(feature = "twitch_oauth2")]
const SCOPE: twitch_oauth2::Validator = twitch_oauth2::validator![];
fn into_topic(self) -> pubsub::Topics { super::Topics::ChannelPointsChannelV1(self) }
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))]
#[non_exhaustive]
pub struct Redemption {
pub channel_id: types::UserId,
pub id: types::RedemptionId,
pub redeemed_at: types::Timestamp,
pub reward: Reward,
pub status: RedemptionStatus,
pub user: types::User,
pub user_input: Option<String>,
pub cursor: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
#[non_exhaustive]
pub enum RedemptionStatus {
Fulfilled,
Unfulfilled,
#[serde(rename = "ACTION_TAKEN")]
ActionTaken,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))]
#[non_exhaustive]
pub struct Reward {
pub background_color: String,
pub channel_id: types::UserId,
pub cooldown_expires_at: Option<types::Timestamp>,
pub cost: u32,
pub default_image: Option<types::Image>,
pub global_cooldown: types::GlobalCooldown,
pub id: types::RewardId,
pub image: Option<types::Image>,
pub is_enabled: bool,
pub is_in_stock: bool,
pub is_paused: bool,
pub is_sub_only: bool,
pub is_user_input_required: bool,
pub max_per_stream: types::Max,
pub max_per_user_per_stream: types::Max,
pub prompt: String,
pub redemptions_redeemed_current_stream: Option<u32>,
pub should_redemptions_skip_request_queue: bool,
pub template_id: Option<String>,
pub title: String,
pub updated_for_indicator_at: Option<types::Timestamp>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))]
#[non_exhaustive]
pub struct Progress {
pub channel_id: types::UserId,
pub id: String,
pub method: String,
pub new_status: RedemptionStatus,
pub processed: i64,
pub total: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(tag = "type", content = "data")]
#[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))]
#[non_exhaustive]
pub enum ChannelPointsChannelV1Reply {
#[serde(rename = "reward-redeemed")]
RewardRedeemed {
timestamp: String,
redemption: Redemption,
},
#[serde(rename = "custom-reward-updated")]
CustomRewardUpdated {
timestamp: String,
updated_reward: Reward,
},
#[serde(rename = "redemption-status-update")]
RedemptionStatusUpdate {
timestamp: String,
redemption: Redemption,
},
#[serde(rename = "update-redemption-statuses-finished")]
UpdateRedemptionStatusesFinished {
timestamp: String,
progress: Progress,
},
#[serde(rename = "update-redemption-statuses-progress")]
UpdateRedemptionStatusProgress {
timestamp: String,
progress: Progress,
},
}
#[cfg(test)]
mod tests {
use super::super::{Response, TopicData};
use super::*;
#[test]
fn channel_point_redeem() {
let message = r##"
{
"type": "reward-redeemed",
"data": {
"timestamp": "2020-10-10T19:13:30.536153182Z",
"redemption": {
"id": "b021f290-bedb-49c2-b90f-e6ceb1c0d4ab",
"user": {
"id": "27620241",
"login": "emilgardis",
"display_name": "emilgardis"
},
"channel_id": "27620241",
"redeemed_at": "2020-10-10T19:13:30.536153182Z",
"reward": {
"id": "252e209d-4f16-4886-a0d1-97f458ad5698",
"channel_id": "27620241",
"title": "Hydration",
"prompt": "Make Emilgardis drink water",
"cost": 2000,
"is_user_input_required": true,
"is_sub_only": false,
"image": null,
"default_image": {
"url_1x": "https://static-cdn.jtvnw.net/custom-reward-images/default-1.png",
"url_2x": "https://static-cdn.jtvnw.net/custom-reward-images/default-2.png",
"url_4x": "https://static-cdn.jtvnw.net/custom-reward-images/default-4.png"
},
"background_color": "#81AEFF",
"is_enabled": true,
"is_paused": false,
"is_in_stock": true,
"max_per_stream": {
"is_enabled": false,
"max_per_stream": 10
},
"should_redemptions_skip_request_queue": false,
"template_id": null,
"updated_for_indicator_at": "2020-02-06T17:29:19.737311439Z",
"max_per_user_per_stream": {
"is_enabled": false,
"max_per_user_per_stream": 0
},
"global_cooldown": {
"is_enabled": false,
"global_cooldown_seconds": 0
},
"redemptions_redeemed_current_stream": 0,
"cooldown_expires_at": null
},
"user_input": "bap",
"status": "UNFULFILLED"
}
}
}
"##;
let source = format!(
r#"{{"type": "MESSAGE","data": {{ "topic": "channel-points-channel-v1.27620241", "message": {message:?} }}}}"#
);
let actual = dbg!(Response::parse(&source).unwrap());
assert!(matches!(
actual,
Response::Message {
data: TopicData::ChannelPointsChannelV1 { .. },
}
));
}
#[test]
fn channel_reward_updated() {
let message = r##"
{
"type": "custom-reward-updated",
"data": {
"timestamp": "2020-10-19T19:40:19.637568468Z",
"updated_reward": {
"id": "071397fb-cd09-420d-8d64-f9fd35f5cdfa",
"channel_id": "27620241",
"title": "Up the difficulty ",
"prompt": "stuff.",
"cost": 20000,
"is_user_input_required": true,
"is_sub_only": false,
"image": null,
"default_image": {
"url_1x": "https://static-cdn.jtvnw.net/custom-reward-images/default-1.png",
"url_2x": "https://static-cdn.jtvnw.net/custom-reward-images/default-2.png",
"url_4x": "https://static-cdn.jtvnw.net/custom-reward-images/default-4.png"
},
"background_color": "#FF6C00",
"is_enabled": true,
"is_paused": true,
"is_in_stock": true,
"max_per_stream": {
"is_enabled": true,
"max_per_stream": 6
},
"should_redemptions_skip_request_queue": false,
"template_id": null,
"updated_for_indicator_at": "2020-06-09T16:02:06.943429808Z",
"max_per_user_per_stream": {
"is_enabled": false,
"max_per_user_per_stream": 0
},
"global_cooldown": {
"is_enabled": false,
"global_cooldown_seconds": 0
},
"redemptions_redeemed_current_stream": 0,
"cooldown_expires_at": null
}
}
}
"##;
let source = format!(
r#"{{"type": "MESSAGE","data": {{ "topic": "channel-points-channel-v1.27620241", "message": {message:?} }}}}"#
);
let actual = dbg!(Response::parse(&source).unwrap());
assert!(matches!(
actual,
Response::Message {
data: TopicData::ChannelPointsChannelV1 { .. },
}
));
}
#[test]
fn redemption_status_update() {
let message = r##"
{
"type": "redemption-status-update",
"data": {
"timestamp": "2020-10-19T19:41:00.590084358Z",
"redemption": {
"id": "844fff0c-6185-44c7-8c30-3d68a565fe1b",
"user": {
"id": "27620241",
"login": "emilgardis",
"display_name": "emilgardis"
},
"channel_id": "27620241",
"redeemed_at": "2020-10-19T15:01:18.453334233Z",
"reward": {
"id": "252e209d-4f16-4886-a0d1-97f458ad5698",
"channel_id": "27620241",
"title": "Hydration",
"prompt": "Make Emilgardis drink water",
"cost": 2000,
"is_user_input_required": false,
"is_sub_only": false,
"image": null,
"default_image": null,
"background_color": "#81AEFF",
"is_enabled": false,
"is_paused": false,
"is_in_stock": false,
"max_per_stream": {
"is_enabled": false,
"max_per_stream": 0
},
"should_redemptions_skip_request_queue": false,
"template_id": null,
"updated_for_indicator_at": null,
"max_per_user_per_stream": {
"is_enabled": false,
"max_per_user_per_stream": 0
},
"global_cooldown": {
"is_enabled": false,
"global_cooldown_seconds": 0
},
"redemptions_redeemed_current_stream": null,
"cooldown_expires_at": null
},
"user_input": "a",
"status": "ACTION_TAKEN",
"cursor": "ODQ0ZmZmMGMtNjE4NS00NGM3LThjMzAtM2Q2OGE1NjVmZTFiX18yMDIwLTEwLTE5VDE1OjAxOjE4LjQ1MzMzNDIzM1o="
}
}
}
"##;
let source = format!(
r#"{{"type": "MESSAGE","data": {{ "topic": "channel-points-channel-v1.27620241", "message": {message:?} }}}}"#
);
let actual = dbg!(Response::parse(&source).unwrap());
assert!(matches!(
actual,
Response::Message {
data: TopicData::ChannelPointsChannelV1 { .. },
}
));
}
#[test]
fn update_redemption_statuses_finished() {
let message = r##"
{
"type": "update-redemption-statuses-finished",
"data": {
"timestamp": "2021-02-15T00:36:55.938819818Z",
"progress": {
"id": "Y29wb0J1bGtFZGl0UmVkZW1wdGlvblN0YXR1c1Byb2dyZXNzOjEzMzc6QlVMS19FRElUX1JFREVNUFRJT05fU1RBVFVTX01FVEhPRF9CWV9DSEFOTkVMOg==",
"channel_id": "1337",
"method": "BY_CHANNEL",
"new_status": "FULFILLED",
"processed": 2,
"total": 2
}
}
}
"##;
let source = format!(
r#"{{"type": "MESSAGE","data": {{ "topic": "channel-points-channel-v1.27620241", "message": {message:?} }}}}"#
);
let actual = dbg!(Response::parse(&source).unwrap());
assert!(matches!(
actual,
Response::Message {
data: TopicData::ChannelPointsChannelV1 { .. },
}
));
}
#[test]
fn update_redemption_statuses_progress() {
let message = r##"
{
"type": "update-redemption-statuses-progress",
"data": {
"timestamp": "2021-02-15T00:36:55.916689548Z",
"progress": {
"id": "Y29wb0J1bGtFZGl0UmVkZW1wdGlvblN0YXR1c1Byb2dyZXNzOjEzMzc6QlVMS19FRElUX1JFREVNUFRJT05fU1RBVFVTX01FVEhPRF9CWV9DSEFOTkVMOg==",
"channel_id": "1337",
"method": "BY_CHANNEL",
"new_status": "FULFILLED",
"processed": 1,
"total": 2
}
}
}
"##;
let source = format!(
r#"{{"type": "MESSAGE","data": {{ "topic": "channel-points-channel-v1.27620241", "message": {message:?} }}}}"#
);
let actual = dbg!(Response::parse(&source).unwrap());
assert!(matches!(
actual,
Response::Message {
data: TopicData::ChannelPointsChannelV1 { .. },
}
));
}
#[test]
fn check_deser() {
use std::convert::TryInto as _;
let s = "channel-points-channel-v1.1234";
assert_eq!(
ChannelPointsChannelV1 { channel_id: 1234 },
s.to_string().try_into().unwrap()
);
}
#[test]
fn check_ser() {
let s = "channel-points-channel-v1.1234";
let right: String = ChannelPointsChannelV1 { channel_id: 1234 }.into();
assert_eq!(s.to_string(), right);
}
}