phemios_core/
network_parental.rs1use super::{Client, Value};
3
4impl Client {
5 pub fn parental_status(&self) -> Result<Option<Value>, String> {
7 self.parental_status_admitted(|| Ok(()))
8 }
9 pub fn parental_status_admitted(
11 &self,
12 current: impl Fn() -> Result<(), String>,
13 ) -> Result<Option<Value>, String> {
14 current()?;
15 if self.account["role"] == "admin" || !self.account["parental_enabled"].is_boolean() {
16 return Ok(None);
17 }
18 let value = self.request_admitted("GET", "/api/me/v1/parental", &[], None, current)?;
19 validate(&value, self.account["id"].as_str().unwrap_or_default())?;
20 Ok(Some(value))
21 }
22 pub fn external_parental_status_admitted(
25 &self,
26 current: impl Fn() -> Result<(), String>,
27 ) -> Result<Option<Value>, String> {
28 current()?;
29 if self.account["role"] != "admin" && self.account["parental_enabled"].is_boolean() {
30 return self.parental_status_admitted(current);
31 }
32 let owner = self.account["id"]
33 .as_str()
34 .filter(|owner| !owner.is_empty())
35 .ok_or("Session Aède invalide. Reconnectez-vous.")?;
36 let role = self.account["role"]
37 .as_str()
38 .filter(|role| matches!(*role, "admin" | "user" | "auditor"))
39 .ok_or("Session Aède invalide. Reconnectez-vous.")?;
40 let live = self.request_admitted("GET", "/api/auth/v1/session", &[], None, ¤t)?;
41 if live["id"] != owner || live["enabled"] != true || live["role"] != role {
42 return Err("Session Aède remplacée, désactivée ou invalide. Reconnectez-vous.".into());
43 }
44 if live
45 .get("parental_enabled")
46 .is_some_and(|marker| !marker.is_boolean())
47 || (self.account["parental_enabled"].is_boolean()
48 && !live["parental_enabled"].is_boolean())
49 {
50 return Err("État du contrôle parental invalide.".into());
51 }
52 if role == "admin" || !live["parental_enabled"].is_boolean() {
53 return Ok(None);
54 }
55 let value = self.request_admitted("GET", "/api/me/v1/parental", &[], None, current)?;
56 validate(&value, owner)?;
57 Ok(Some(value))
58 }
59 pub fn admit_snapshot(&self, snapshot: &Value) -> Result<(), String> {
61 self.admit_snapshot_admitted(snapshot, || Ok(()))
62 }
63 pub fn admit_snapshot_admitted(
65 &self,
66 snapshot: &Value,
67 current: impl Fn() -> Result<(), String>,
68 ) -> Result<(), String> {
69 if let Some(status) = self.parental_status_admitted(current)? {
70 admit(
71 &status,
72 snapshot,
73 self.account["id"].as_str().unwrap_or_default(),
74 )?;
75 }
76 Ok(())
77 }
78 pub fn admit_artwork_snapshot(&self, snapshot: &Value) -> Result<(), String> {
84 self.admit_artwork_snapshot_admitted(snapshot, || Ok(()))
85 }
86 pub fn admit_artwork_snapshot_admitted(
88 &self,
89 snapshot: &Value,
90 current: impl Fn() -> Result<(), String>,
91 ) -> Result<(), String> {
92 current()?;
93 if self.account["role"] == "admin" || !self.account["parental_enabled"].is_boolean() {
94 return Ok(());
95 }
96 let mut cached = self
97 .artwork_parental
98 .lock()
99 .map_err(|_| "État du contrôle parental indisponible.")?;
100 if cached
101 .as_ref()
102 .is_some_and(|(checked, _)| checked.elapsed() >= std::time::Duration::from_secs(1))
103 {
104 *cached = None;
105 }
106 if cached.is_none() {
107 let checked = std::time::Instant::now();
109 if let Some(status) = self.parental_status_admitted(¤t)? {
110 *cached = Some((checked, status));
111 }
112 }
113 if let Some((_, status)) = cached.as_ref() {
114 admit(
115 status,
116 snapshot,
117 self.account["id"].as_str().unwrap_or_default(),
118 )?;
119 }
120 Ok(())
121 }
122 pub fn require_unrestricted_download(&self) -> Result<(), String> {
124 self.require_unrestricted_download_admitted(|| Ok(()))
125 }
126 pub fn require_unrestricted_download_admitted(
128 &self,
129 current: impl Fn() -> Result<(), String>,
130 ) -> Result<(), String> {
131 if self
132 .external_parental_status_admitted(current)?
133 .is_some_and(|v| v["policy"]["enabled"] == true)
134 {
135 return Err(
136 "Le téléchargement des épisodes est indisponible pour un compte supervisé.".into(),
137 );
138 }
139 Ok(())
140 }
141 pub fn require_unrestricted_output(&self) -> Result<(), String> {
143 self.require_unrestricted_output_admitted(|| Ok(()))
144 }
145 pub fn require_unrestricted_output_admitted(
147 &self,
148 current: impl Fn() -> Result<(), String>,
149 ) -> Result<(), String> {
150 if self
151 .external_parental_status_admitted(current)?
152 .is_some_and(|v| v["policy"]["enabled"] == true)
153 {
154 return Err("HQPlayer est indisponible pour un compte supervisé.".into());
155 }
156 Ok(())
157 }
158}
159
160fn admit(status: &Value, snapshot: &Value, owner: &str) -> Result<(), String> {
161 if status["policy"]["enabled"] == true
162 && (snapshot["accountId"] != owner
163 || snapshot["parentalEnabled"] != true
164 || snapshot["parentalRevision"] != status["revision"]
165 || snapshot["parentalPolicy"] != status["policy"])
166 {
167 return Err("Catalogue supervisé à actualiser. Synchronisez la bibliothèque.".into());
168 }
169 Ok(())
170}
171fn validate(value: &Value, owner: &str) -> Result<(), String> {
172 if value["owner"] != owner
173 || !value["policy"]["enabled"].is_boolean()
174 || value["revision"].as_u64().is_none()
175 || !value["allowed"].is_boolean()
176 {
177 return Err("État du contrôle parental invalide ou réservé à un autre compte.".into());
178 }
179 Ok(())
180}
181#[cfg(test)]
182#[path = "network_parental_tests.rs"]
183mod tests;