sponsor_block/client/user/
user_stats.rs

1// Uses
2use std::{collections::HashMap, result::Result as StdResult};
3
4use serde::{Deserialize, Deserializer};
5use serde_json::from_str as from_json_str;
6
7use crate::{
8	api::{convert_to_action_type, convert_to_segment_kind},
9	error::Result,
10	util::{de::map_hashmap_key_from_str, get_response_text},
11	Action,
12	ActionableSegmentKind,
13	Client,
14	LocalUserIdSlice,
15	PublicUserId,
16	PublicUserIdSlice,
17};
18
19/// The results of a user info request.
20#[derive(Deserialize, Debug, Default)]
21#[serde(default, rename_all = "camelCase")]
22pub struct UserStats {
23	/// The user's public user ID.
24	#[serde(rename = "userID")]
25	pub user_id: PublicUserId,
26	/// The user's username.
27	pub user_name: Option<String>,
28	/// The overall stats for the user.
29	pub overall_stats: OverallStats,
30	/// The categories with associated segment counts.
31	#[serde(deserialize_with = "map_category_kinds")]
32	pub category_count: HashMap<ActionableSegmentKind, u32>,
33	/// The action types with associated segment counts.
34	#[serde(deserialize_with = "map_action_types")]
35	pub action_type_count: HashMap<Action, u32>,
36}
37
38fn map_category_kinds<'de, D: Deserializer<'de>, O: Deserialize<'de>>(
39	deserializer: D,
40) -> StdResult<HashMap<ActionableSegmentKind, O>, D::Error> {
41	map_hashmap_key_from_str(deserializer, convert_to_segment_kind)
42}
43
44fn map_action_types<'de, D: Deserializer<'de>, O: Deserialize<'de>>(
45	deserializer: D,
46) -> StdResult<HashMap<Action, O>, D::Error> {
47	map_hashmap_key_from_str(deserializer, convert_to_action_type)
48}
49
50/// The overall stats for a user, similar to what [`UserInfo`] provides.
51///
52/// TODO: Find a nice way to remove this. <https://github.com/serde-rs/serde/issues/2115>
53///
54/// [`UserInfo`]: super::user_info::UserInfo
55#[derive(Deserialize, Debug, Default)]
56#[serde(default, rename_all = "camelCase")]
57pub struct OverallStats {
58	/// The number of minutes this user has saved other users.
59	pub minutes_saved: f32,
60	/// The total number of segments submitted, excluding ignored & hidden
61	/// segments.
62	pub segment_count: u32,
63}
64
65// Function Constants
66const API_ENDPOINT: &str = "/userStats";
67
68// Function Implementation
69impl Client {
70	/// Fetches a user's info using a public user ID.
71	///
72	/// # Errors
73	/// Can return pretty much any error type from [`SponsorBlockError`]. See
74	/// the error type definitions for explanations of when they might be
75	/// encountered.
76	///
77	/// [`SponsorBlockError`]: crate::SponsorBlockError
78	pub async fn fetch_user_stats_public(
79		&self,
80		public_user_id: &PublicUserIdSlice,
81	) -> Result<UserStats> {
82		// Build the request
83		let request = self
84			.http
85			.get(format!("{}{}", &self.base_url, API_ENDPOINT))
86			.query(&[("publicUserID", public_user_id)])
87			.query(&[("fetchCategoryStats", true), ("fetchActionTypeStats", true)]);
88
89		// Send the request
90		let response = get_response_text(request.send().await?).await?;
91
92		// Parse the response
93		let mut result = from_json_str::<UserStats>(response.as_str())?;
94		// The user name is set to the public user ID if not set. This converts it to a
95		// more idiomatic value transparently.
96		if result
97			.user_name
98			.as_ref()
99			.expect("userName field was not set")
100			.eq(&result.user_id)
101		{
102			result.user_name = None;
103		}
104		Ok(result)
105	}
106
107	/// Fetches a user's info using a local (private) user ID.
108	///
109	/// # Errors
110	/// Can return pretty much any error type from [`SponsorBlockError`]. See
111	/// the error type definitions for explanations of when they might be
112	/// encountered.
113	///
114	/// [`SponsorBlockError`]: crate::SponsorBlockError
115	pub async fn fetch_user_stats_local(
116		&self,
117		local_user_id: &LocalUserIdSlice,
118	) -> Result<UserStats> {
119		// Build the request
120		let request = self
121			.http
122			.get(format!("{}{}", &self.base_url, API_ENDPOINT))
123			.query(&[("userID", local_user_id)])
124			.query(&[("fetchCategoryStats", true), ("fetchActionTypeStats", true)]);
125
126		// Send the request
127		let response = get_response_text(request.send().await?).await?;
128
129		// Parse the response
130		let mut result = from_json_str::<UserStats>(response.as_str())?;
131		// The user name is set to the public user ID if not set. This converts it to a
132		// more idiomatic value transparently.
133		if result
134			.user_name
135			.as_ref()
136			.expect("userName field was not set")
137			.eq(&result.user_id)
138		{
139			result.user_name = None;
140		}
141		Ok(result)
142	}
143}