Skip to content

Commit 2a12535

Browse files
committed
feat(helix): add Get User Active Extensions
1 parent f2bee12 commit 2a12535

File tree

4 files changed

+445
-4
lines changed

4 files changed

+445
-4
lines changed

src/helix/client/client_ext.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1358,6 +1358,21 @@ impl<'client, C: crate::HttpClient + Sync + 'client> HelixClient<'client, C> {
13581358
Ok(self.req_get(req, token).await?.data)
13591359
}
13601360

1361+
/// Gets the active extensions that the broadcaster has installed for each configuration.
1362+
///
1363+
/// The user ID in the access token identifies the broadcaster.
1364+
pub async fn get_user_active_extensions<'b, T>(
1365+
&'client self,
1366+
token: &T,
1367+
) -> Result<helix::users::ExtensionConfiguration, ClientError<C>>
1368+
where
1369+
T: TwitchToken + Send + Sync + ?Sized,
1370+
{
1371+
let req = helix::users::GetUserActiveExtensionsRequest::new();
1372+
1373+
Ok(self.req_get(req, token).await?.data)
1374+
}
1375+
13611376
/// Retrieves the active shared chat session for a channel
13621377
///
13631378
/// [`None`] is returned if no shared chat session is active.
Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
//! Gets the active extensions that the broadcaster has installed for each configuration.
2+
//! [`get-user-active-extensions`](https://dev.twitch.tv/docs/api/reference#get-user-active-extensions)
3+
//!
4+
//! ## Request: [GetUserActiveExtensionsRequest]
5+
//!
6+
//! To use this endpoint, construct a [`GetUserActiveExtensionsRequest`] with the [`GetUserActiveExtensionsRequest::new()`] method.
7+
//!
8+
//! ```rust
9+
//! use twitch_api::helix::users::get_user_active_extensions;
10+
//! let request =
11+
//! get_user_active_extensions::GetUserActiveExtensionsRequest::new();
12+
//! ```
13+
//!
14+
//! ## Response: [ExtensionConfiguration]
15+
//!
16+
//! Send the request to receive the response with [`HelixClient::req_get()`](helix::HelixClient::req_get).
17+
//!
18+
//! ```rust, no_run
19+
//! use twitch_api::helix::{self, users::get_user_active_extensions};
20+
//! # use twitch_api::client;
21+
//! # #[tokio::main]
22+
//! # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
23+
//! # let client: helix::HelixClient<'static, client::DummyHttpClient> = helix::HelixClient::default();
24+
//! # let token = twitch_oauth2::AccessToken::new("validtoken".to_string());
25+
//! # let token = twitch_oauth2::UserToken::from_existing(&client, token, None, None).await?;
26+
//! let request = get_user_active_extensions::GetUserActiveExtensionsRequest::new();
27+
//! let response: get_user_active_extensions::ExtensionConfiguration = client.req_get(request, &token).await?.data;
28+
//! # Ok(())
29+
//! # }
30+
//! ```
31+
//!
32+
//! You can also get the [`http::Request`] with [`request.create_request(&token, &client_id)`](helix::RequestGet::create_request)
33+
//! and parse the [`http::Response`] with [`GetUserActiveExtensionsRequest::parse_response(None, &request.get_uri(), response)`](GetUserActiveExtensionsRequest::parse_response)
34+
35+
use std::collections::HashMap;
36+
37+
use super::*;
38+
use helix::RequestGet;
39+
use serde::{Deserialize, Serialize};
40+
41+
/// Query Parameters for [Get User Active Extensions](super::get_user_active_extensions)
42+
///
43+
/// [`get-user-active-extensions`](https://dev.twitch.tv/docs/api/reference#get-user-active-extensions)
44+
#[derive(PartialEq, Eq, Deserialize, Serialize, Clone, Debug, Default)]
45+
#[cfg_attr(feature = "typed-builder", derive(typed_builder::TypedBuilder))]
46+
#[must_use]
47+
#[non_exhaustive]
48+
pub struct GetUserActiveExtensionsRequest<'a> {
49+
/// The ID of the broadcaster whose active extensions you want to get.
50+
///
51+
/// 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.
52+
#[cfg_attr(feature = "typed-builder", builder(default, setter(into)))]
53+
#[cfg_attr(feature = "deser_borrow", serde(borrow = "'a"))]
54+
pub user_id: Option<Cow<'a, types::UserIdRef>>,
55+
}
56+
57+
impl<'a> GetUserActiveExtensionsRequest<'a> {
58+
/// Gets the active extensions that the broadcaster has installed for each configuration.
59+
///
60+
/// Requires a user access token.
61+
pub fn new() -> Self { Self::default() }
62+
63+
/// Gets the active extensions that the user has installed for each configuration.
64+
///
65+
/// Requires an app access token.
66+
pub fn user_id(user_id: impl types::IntoCow<'a, types::UserIdRef> + 'a) -> Self {
67+
Self {
68+
user_id: Some(user_id.into_cow()),
69+
}
70+
}
71+
}
72+
73+
/// Return Values for [Get User Active Extensions](super::get_user_active_extensions)
74+
///
75+
/// [`get-user-active-extensions`](https://dev.twitch.tv/docs/api/reference#get-user-active-extensions)
76+
#[derive(PartialEq, Eq, Deserialize, Serialize, Debug, Clone)]
77+
#[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))]
78+
#[non_exhaustive]
79+
pub struct ExtensionConfiguration {
80+
/// A dictionary that contains the data for a panel extension.
81+
///
82+
/// The dictionary’s key is a sequential number beginning with 1.
83+
pub panel: HashMap<String, ExtensionSlot<ActiveExtension>>,
84+
/// A dictionary that contains the data for a video-overlay extension.
85+
///
86+
/// The dictionary’s key is a sequential number beginning with 1.
87+
pub overlay: HashMap<String, ExtensionSlot<ActiveExtension>>,
88+
/// A dictionary that contains the data for a video-component extension.
89+
///
90+
/// The dictionary’s key is a sequential number beginning with 1.
91+
pub component: HashMap<String, ExtensionSlot<ActivePositionedExtension>>,
92+
}
93+
94+
/// An active extension slot
95+
#[derive(PartialEq, Eq, Deserialize, Serialize, Debug, Clone)]
96+
#[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))]
97+
#[non_exhaustive]
98+
pub struct ActiveExtension {
99+
/// An ID that identifies the extension.
100+
pub id: types::ExtensionId,
101+
/// The extension’s version.
102+
pub version: String,
103+
/// The extension’s name.
104+
pub name: String,
105+
}
106+
107+
/// An active extension slot where the extension can be positioned
108+
#[derive(PartialEq, Eq, Deserialize, Serialize, Debug, Clone)]
109+
#[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))]
110+
#[non_exhaustive]
111+
pub struct ActivePositionedExtension {
112+
/// An ID that identifies the extension.
113+
pub id: types::ExtensionId,
114+
/// The extension’s version.
115+
pub version: String,
116+
/// The extension’s name.
117+
pub name: String,
118+
/// The x-coordinate where the extension is placed.
119+
pub x: i32,
120+
/// The y-coordinate where the extension is placed.
121+
pub y: i32,
122+
}
123+
124+
impl Request for GetUserActiveExtensionsRequest<'_> {
125+
type Response = ExtensionConfiguration;
126+
127+
#[cfg(feature = "twitch_oauth2")]
128+
const OPT_SCOPE: &'static [twitch_oauth2::Scope] =
129+
&[twitch_oauth2::Scope::UserReadBlockedUsers];
130+
const PATH: &'static str = "users/extensions";
131+
#[cfg(feature = "twitch_oauth2")]
132+
const SCOPE: twitch_oauth2::Validator = twitch_oauth2::validator![any(
133+
twitch_oauth2::Scope::UserReadBroadcast,
134+
twitch_oauth2::Scope::UserEditBroadcast
135+
)];
136+
}
137+
138+
impl RequestGet for GetUserActiveExtensionsRequest<'_> {}
139+
140+
#[cfg(test)]
141+
#[test]
142+
fn test_request() {
143+
use helix::*;
144+
let req = GetUserActiveExtensionsRequest::new();
145+
146+
let data = br#"
147+
{
148+
"data": {
149+
"panel": {
150+
"1": {
151+
"active": true,
152+
"id": "rh6jq1q334hqc2rr1qlzqbvwlfl3x0",
153+
"version": "1.1.0",
154+
"name": "TopClip"
155+
},
156+
"2": {
157+
"active": true,
158+
"id": "wi08ebtatdc7oj83wtl9uxwz807l8b",
159+
"version": "1.1.8",
160+
"name": "Streamlabs Leaderboard"
161+
},
162+
"3": {
163+
"active": true,
164+
"id": "naty2zwfp7vecaivuve8ef1hohh6bo",
165+
"version": "1.0.9",
166+
"name": "Streamlabs Stream Schedule & Countdown"
167+
}
168+
},
169+
"overlay": {
170+
"1": {
171+
"active": true,
172+
"id": "zfh2irvx2jb4s60f02jq0ajm8vwgka",
173+
"version": "1.0.19",
174+
"name": "Streamlabs"
175+
}
176+
},
177+
"component": {
178+
"1": {
179+
"active": true,
180+
"id": "lqnf3zxk0rv0g7gq92mtmnirjz2cjj",
181+
"version": "0.0.1",
182+
"name": "Dev Experience Test",
183+
"x": 0,
184+
"y": 0
185+
},
186+
"2": {
187+
"active": false
188+
}
189+
}
190+
}
191+
}
192+
"#
193+
.to_vec();
194+
195+
let http_response = http::Response::builder().body(data).unwrap();
196+
197+
let uri = req.get_uri().unwrap();
198+
assert_eq!(
199+
uri.to_string(),
200+
"https://api.twitch.tv/helix/users/extensions?"
201+
);
202+
203+
let res = GetUserActiveExtensionsRequest::parse_response(Some(req), &uri, http_response)
204+
.unwrap()
205+
.data;
206+
assert_eq!(res.panel.len(), 3);
207+
assert_eq!(res.overlay.len(), 1);
208+
assert_eq!(res.component.len(), 2);
209+
210+
assert_eq!(
211+
*res.overlay.get("1").unwrap(),
212+
ExtensionSlot::Active(ActiveExtension {
213+
id: "zfh2irvx2jb4s60f02jq0ajm8vwgka".into(),
214+
version: "1.0.19".to_owned(),
215+
name: "Streamlabs".to_owned(),
216+
})
217+
);
218+
assert_eq!(
219+
*res.component.get("1").unwrap(),
220+
ExtensionSlot::Active(ActivePositionedExtension {
221+
id: "lqnf3zxk0rv0g7gq92mtmnirjz2cjj".into(),
222+
version: "0.0.1".to_owned(),
223+
name: "Dev Experience Test".to_owned(),
224+
x: 0,
225+
y: 0,
226+
})
227+
);
228+
assert_eq!(*res.component.get("2").unwrap(), ExtensionSlot::Inactive);
229+
230+
assert_eq!(
231+
res,
232+
serde_json::from_str(&serde_json::to_string(&res).unwrap()).unwrap()
233+
);
234+
}

0 commit comments

Comments
 (0)