Skip to content

feat(helix): Implement remaining Schedule and User endpoints #460

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Dec 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
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)
}

Check warning on line 1359 in src/helix/client/client_ext.rs

View check run for this annotation

Codecov / codecov/patch

src/helix/client/client_ext.rs#L1349-L1359

Added lines #L1349 - L1359 were not covered by tests

/// 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)
}

Check warning on line 1374 in src/helix/client/client_ext.rs

View check run for this annotation

Codecov / codecov/patch

src/helix/client/client_ext.rs#L1364-L1374

Added lines #L1364 - L1374 were not covered by tests

/// 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)]

Check warning on line 44 in src/helix/endpoints/users/get_user_active_extensions.rs

View check run for this annotation

Codecov / codecov/patch

src/helix/endpoints/users/get_user_active_extensions.rs#L44

Added line #L44 was not covered by tests
#[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()),
}
}

Check warning on line 70 in src/helix/endpoints/users/get_user_active_extensions.rs

View check run for this annotation

Codecov / codecov/patch

src/helix/endpoints/users/get_user_active_extensions.rs#L66-L70

Added lines #L66 - L70 were not covered by tests
}

/// 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
Loading