use twitch_api2::{ helix::points::{CreateCustomRewardRequest, CreateCustomRewardBody, GetCustomRewardRequest, CustomReward}, types::RewardId, }; use warp::{ Filter, Reply, filters::BoxedFilter, http::Uri, }; use uuid::Uuid; use crate::app::{ State, config::Redemption, web::{ CustomRejection, template::redemptions::{RedemptionsTemplate, AddRedemptionTemplate, CreateRedemptionTemplate, ListRedemptionsTemplate}, }, }; use std::{ collections::HashMap, convert::Infallible, sync::Arc, }; pub fn redemptions_routes(state: Arc) -> BoxedFilter<(impl Reply, )> { warp::get() .and( redemptions_get(Arc::clone(&state)) .or(redemptions_add_get()) .or(redemptions_list_get(Arc::clone(&state))) .or(redemptions_create_get()) ) .or(warp::post().and( redemptions_add_post(Arc::clone(&state)) .or(redemptions_delete_post(Arc::clone(&state))) .or(redemptions_create_post(Arc::clone(&state))) )) .boxed() } fn redemptions_get(state: Arc) -> BoxedFilter<(impl Reply, )> { warp::path("redemptions") .and(warp::path::end()) .and_then(move || { let state = Arc::clone(&state); async move { Result::::Ok(RedemptionsTemplate { redemptions: state.config.read().await.redemptions.clone(), }) } }) .boxed() } fn redemptions_add_get() -> BoxedFilter<(impl Reply, )> { warp::path("redemptions") .and(warp::path("add")) .and(warp::path::end()) .map(|| AddRedemptionTemplate) .boxed() } fn redemptions_add_post(state: Arc) -> BoxedFilter<(impl Reply, )> { warp::path("redemptions") .and(warp::path("add")) .and(warp::path::end()) .and(warp::body::content_length_limit(1024 * 5)) .and(warp::body::form()) .and_then(move |mut form: HashMap| { let state = Arc::clone(&state); async move { let form_get = try { let name = form.remove("name")?; let twitch_id = form.remove("twitch_id")?; let rhai = form.remove("rhai")?; (name, twitch_id, rhai) }; let (name, twitch_id, rhai) = match form_get { Some(x) => x, None => return Err(warp::reject::custom(CustomRejection::InvalidForm)), }; let redemption = Redemption { id: Uuid::new_v4(), name, twitch_id: RewardId::new(twitch_id), rhai, }; state.config.write().await.redemptions.push(redemption); Ok(warp::redirect(Uri::from_static("/redemptions"))) } }) .boxed() } fn redemptions_create_get() -> BoxedFilter<(impl Reply, )> { warp::path("redemptions") .and(warp::path("create")) .and(warp::path::end()) .map(|| CreateRedemptionTemplate) .boxed() } fn redemptions_create_post(state: Arc) -> BoxedFilter<(impl Reply, )> { warp::path("redemptions") .and(warp::path("create")) .and(warp::path::end()) .and(warp::body::content_length_limit(1024 * 5)) .and(warp::body::form()) .and_then(move |mut form: HashMap| { let state = Arc::clone(&state); async move { let form_get = try { let name = form.remove("name")?; name }; let name = match form_get { Some(x) => x, None => return Err(warp::reject::custom(CustomRejection::InvalidForm)), }; let request = CreateCustomRewardRequest::builder() .broadcaster_id(state.user_config.twitch.channel_id.to_string()) .build(); let body = CreateCustomRewardBody::builder() .title(name) .cost(100) .build(); let token = match state.twitch.user_token().await { Ok(token) => token, Err(_) => return Err(warp::reject::custom(CustomRejection::TwitchError)), }; let _reward: CustomReward = match state.twitch.client.helix.req_post(request, body, &token).await { Ok(resp) => resp.data, Err(_) => return Err(warp::reject::custom(CustomRejection::TwitchError)), }; Ok(warp::redirect(Uri::from_static("/redemptions/list"))) } }) .boxed() } fn redemptions_delete_post(state: Arc) -> BoxedFilter<(impl Reply, )> { warp::path("redemptions") .and(warp::path("delete")) .and(warp::path::end()) .and(warp::body::content_length_limit(1024 * 5)) .and(warp::body::form()) .and_then(move |mut form: HashMap| { let state = Arc::clone(&state); async move { let id = match super::uuid_from_form(&mut form) { Some(n) => n, None => return Err(warp::reject::custom(CustomRejection::InvalidForm)), }; state.config.write().await.redemptions.drain_filter(|redemption| redemption.id == id); Ok(warp::redirect(Uri::from_static("/redemptions"))) } }) .boxed() } fn redemptions_list_get(state: Arc) -> BoxedFilter<(impl Reply, )> { warp::path("redemptions") .and(warp::path("list")) .and(warp::path::end()) .and_then( move || { let state = Arc::clone(&state); async move { let req = GetCustomRewardRequest::builder() .broadcaster_id(state.user_config.twitch.channel_id.to_string()) .build(); let token = match state.twitch.user_token().await { Ok(token) => token, Err(_) => return Err(warp::reject::custom(CustomRejection::TwitchError)), }; let rewards: Vec = match state.twitch.client.helix.req_get(req, &token).await { Ok(resp) => resp.data, Err(_) => return Err(warp::reject::custom(CustomRejection::TwitchError)), }; Ok(rewards) } }) .map(|rewards: Vec| ListRedemptionsTemplate { rewards, }) .boxed() }