Skip to content

Commit

Permalink
Merge pull request #460 from twitch-rs/feat/remaining-helix
Browse files Browse the repository at this point in the history
feat(helix): Implement remaining Schedule and User endpoints
  • Loading branch information
Nerixyz authored Dec 13, 2024
2 parents 866cd24 + 47950a5 commit 13e2c4c
Show file tree
Hide file tree
Showing 7 changed files with 1,045 additions and 8 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ helix = [
"twitch_types/chat",
"twitch_types/color",
"twitch_types/emote",
"twitch_types/extension",
"twitch_types/goal",
"twitch_types/moderation",
"twitch_types/points",
Expand Down
30 changes: 30 additions & 0 deletions src/helix/client/client_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1343,6 +1343,36 @@ impl<'client, C: crate::HttpClient + Sync + 'client> HelixClient<'client, C> {
Ok(self.req_put(req, helix::EmptyBody, token).await?.data)
}

/// Gets a list of all extensions (both active and inactive) that the broadcaster has installed.
///
/// The user ID in the access token identifies the broadcaster.
pub async fn get_user_extensions<'b, T>(
&'client self,
token: &T,
) -> Result<Vec<helix::users::Extension>, ClientError<C>>
where
T: TwitchToken + Send + Sync + ?Sized,
{
let req = helix::users::GetUserExtensionsRequest::new();

Ok(self.req_get(req, token).await?.data)
}

/// Gets the active extensions that the broadcaster has installed for each configuration.
///
/// The user ID in the access token identifies the broadcaster.
pub async fn get_user_active_extensions<'b, T>(
&'client self,
token: &T,
) -> Result<helix::users::ExtensionConfiguration, ClientError<C>>
where
T: TwitchToken + Send + Sync + ?Sized,
{
let req = helix::users::GetUserActiveExtensionsRequest::new();

Ok(self.req_get(req, token).await?.data)
}

/// Retrieves the active shared chat session for a channel
///
/// [`None`] is returned if no shared chat session is active.
Expand Down
234 changes: 234 additions & 0 deletions src/helix/endpoints/users/get_user_active_extensions.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
//! Gets the active extensions that the broadcaster has installed for each configuration.
//! [`get-user-active-extensions`](https://dev.twitch.tv/docs/api/reference#get-user-active-extensions)
//!
//! ## Request: [GetUserActiveExtensionsRequest]
//!
//! To use this endpoint, construct a [`GetUserActiveExtensionsRequest`] with the [`GetUserActiveExtensionsRequest::new()`] method.
//!
//! ```rust
//! use twitch_api::helix::users::get_user_active_extensions;
//! let request =
//! get_user_active_extensions::GetUserActiveExtensionsRequest::new();
//! ```
//!
//! ## Response: [ExtensionConfiguration]
//!
//! Send the request to receive the response with [`HelixClient::req_get()`](helix::HelixClient::req_get).
//!
//! ```rust, no_run
//! use twitch_api::helix::{self, users::get_user_active_extensions};
//! # use twitch_api::client;
//! # #[tokio::main]
//! # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
//! # let client: helix::HelixClient<'static, client::DummyHttpClient> = helix::HelixClient::default();
//! # let token = twitch_oauth2::AccessToken::new("validtoken".to_string());
//! # let token = twitch_oauth2::UserToken::from_existing(&client, token, None, None).await?;
//! let request = get_user_active_extensions::GetUserActiveExtensionsRequest::new();
//! let response: get_user_active_extensions::ExtensionConfiguration = client.req_get(request, &token).await?.data;
//! # Ok(())
//! # }
//! ```
//!
//! You can also get the [`http::Request`] with [`request.create_request(&token, &client_id)`](helix::RequestGet::create_request)
//! and parse the [`http::Response`] with [`GetUserActiveExtensionsRequest::parse_response(None, &request.get_uri(), response)`](GetUserActiveExtensionsRequest::parse_response)
use std::collections::HashMap;

use super::*;
use helix::RequestGet;
use serde::{Deserialize, Serialize};

/// Query Parameters for [Get User Active Extensions](super::get_user_active_extensions)
///
/// [`get-user-active-extensions`](https://dev.twitch.tv/docs/api/reference#get-user-active-extensions)
#[derive(PartialEq, Eq, Deserialize, Serialize, Clone, Debug, Default)]
#[cfg_attr(feature = "typed-builder", derive(typed_builder::TypedBuilder))]
#[must_use]
#[non_exhaustive]
pub struct GetUserActiveExtensionsRequest<'a> {
/// The ID of the broadcaster whose active extensions you want to get.
///
/// This parameter is required if you specify an app access token and is optional if you specify a user access token. If you specify a user access token and don’t specify this parameter, the API uses the user ID from the access token.
#[cfg_attr(feature = "typed-builder", builder(default, setter(into)))]
#[cfg_attr(feature = "deser_borrow", serde(borrow = "'a"))]
pub user_id: Option<Cow<'a, types::UserIdRef>>,
}

impl<'a> GetUserActiveExtensionsRequest<'a> {
/// Gets the active extensions that the broadcaster has installed for each configuration.
///
/// Requires a user access token.
pub fn new() -> Self { Self::default() }

/// Gets the active extensions that the user has installed for each configuration.
///
/// Requires an app access token.
pub fn user_id(user_id: impl types::IntoCow<'a, types::UserIdRef> + 'a) -> Self {
Self {
user_id: Some(user_id.into_cow()),
}
}
}

/// Return Values for [Get User Active Extensions](super::get_user_active_extensions)
///
/// [`get-user-active-extensions`](https://dev.twitch.tv/docs/api/reference#get-user-active-extensions)
#[derive(PartialEq, Eq, Deserialize, Serialize, Debug, Clone)]
#[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))]
#[non_exhaustive]
pub struct ExtensionConfiguration {
/// A dictionary that contains the data for a panel extension.
///
/// The dictionary’s key is a sequential number beginning with 1.
pub panel: HashMap<String, ExtensionSlot<ActiveExtension>>,
/// A dictionary that contains the data for a video-overlay extension.
///
/// The dictionary’s key is a sequential number beginning with 1.
pub overlay: HashMap<String, ExtensionSlot<ActiveExtension>>,
/// A dictionary that contains the data for a video-component extension.
///
/// The dictionary’s key is a sequential number beginning with 1.
pub component: HashMap<String, ExtensionSlot<ActivePositionedExtension>>,
}

/// An active extension slot
#[derive(PartialEq, Eq, Deserialize, Serialize, Debug, Clone)]
#[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))]
#[non_exhaustive]
pub struct ActiveExtension {
/// An ID that identifies the extension.
pub id: types::ExtensionId,
/// The extension’s version.
pub version: String,
/// The extension’s name.
pub name: String,
}

/// An active extension slot where the extension can be positioned
#[derive(PartialEq, Eq, Deserialize, Serialize, Debug, Clone)]
#[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))]
#[non_exhaustive]
pub struct ActivePositionedExtension {
/// An ID that identifies the extension.
pub id: types::ExtensionId,
/// The extension’s version.
pub version: String,
/// The extension’s name.
pub name: String,
/// The x-coordinate where the extension is placed.
pub x: i32,
/// The y-coordinate where the extension is placed.
pub y: i32,
}

impl Request for GetUserActiveExtensionsRequest<'_> {
type Response = ExtensionConfiguration;

#[cfg(feature = "twitch_oauth2")]
const OPT_SCOPE: &'static [twitch_oauth2::Scope] =
&[twitch_oauth2::Scope::UserReadBlockedUsers];
const PATH: &'static str = "users/extensions";
#[cfg(feature = "twitch_oauth2")]
const SCOPE: twitch_oauth2::Validator = twitch_oauth2::validator![any(
twitch_oauth2::Scope::UserReadBroadcast,
twitch_oauth2::Scope::UserEditBroadcast
)];
}

impl RequestGet for GetUserActiveExtensionsRequest<'_> {}

#[cfg(test)]
#[test]
fn test_request() {
use helix::*;
let req = GetUserActiveExtensionsRequest::new();

let data = br#"
{
"data": {
"panel": {
"1": {
"active": true,
"id": "rh6jq1q334hqc2rr1qlzqbvwlfl3x0",
"version": "1.1.0",
"name": "TopClip"
},
"2": {
"active": true,
"id": "wi08ebtatdc7oj83wtl9uxwz807l8b",
"version": "1.1.8",
"name": "Streamlabs Leaderboard"
},
"3": {
"active": true,
"id": "naty2zwfp7vecaivuve8ef1hohh6bo",
"version": "1.0.9",
"name": "Streamlabs Stream Schedule & Countdown"
}
},
"overlay": {
"1": {
"active": true,
"id": "zfh2irvx2jb4s60f02jq0ajm8vwgka",
"version": "1.0.19",
"name": "Streamlabs"
}
},
"component": {
"1": {
"active": true,
"id": "lqnf3zxk0rv0g7gq92mtmnirjz2cjj",
"version": "0.0.1",
"name": "Dev Experience Test",
"x": 0,
"y": 0
},
"2": {
"active": false
}
}
}
}
"#
.to_vec();

let http_response = http::Response::builder().body(data).unwrap();

let uri = req.get_uri().unwrap();
assert_eq!(
uri.to_string(),
"https://api.twitch.tv/helix/users/extensions?"
);

let res = GetUserActiveExtensionsRequest::parse_response(Some(req), &uri, http_response)
.unwrap()
.data;
assert_eq!(res.panel.len(), 3);
assert_eq!(res.overlay.len(), 1);
assert_eq!(res.component.len(), 2);

assert_eq!(
*res.overlay.get("1").unwrap(),
ExtensionSlot::Active(ActiveExtension {
id: "zfh2irvx2jb4s60f02jq0ajm8vwgka".into(),
version: "1.0.19".to_owned(),
name: "Streamlabs".to_owned(),
})
);
assert_eq!(
*res.component.get("1").unwrap(),
ExtensionSlot::Active(ActivePositionedExtension {
id: "lqnf3zxk0rv0g7gq92mtmnirjz2cjj".into(),
version: "0.0.1".to_owned(),
name: "Dev Experience Test".to_owned(),
x: 0,
y: 0,
})
);
assert_eq!(*res.component.get("2").unwrap(), ExtensionSlot::Inactive);

assert_eq!(
res,
serde_json::from_str(&serde_json::to_string(&res).unwrap()).unwrap()
);
}
Loading

0 comments on commit 13e2c4c

Please sign in to comment.