Coverage for icloudpy/services/account.py: 95%

198 statements  

« prev     ^ index     » next       coverage.py v7.10.7, created at 2025-11-02 05:49 +0000

1"""Account service.""" 

2from collections import OrderedDict 

3 

4from icloudpy.utils import underscore_to_camelcase 

5 

6 

7class AccountService: 

8 """The 'Account' iCloud service.""" 

9 

10 def __init__(self, service_root, session, params): 

11 self.session = session 

12 self.params = params 

13 self._service_root = service_root 

14 

15 self._devices = [] 

16 self._family = [] 

17 self._storage = None 

18 

19 self._acc_endpoint = f"{self._service_root}/setup/web" 

20 self._acc_devices_url = f"{self._acc_endpoint}/device/getDevices" 

21 self._acc_family_details_url = f"{self._acc_endpoint}/family/getFamilyDetails" 

22 self._acc_family_member_photo_url = ( 

23 f"{self._acc_endpoint}/family/getMemberPhoto" 

24 ) 

25 

26 self._acc_storage_url = "https://setup.icloud.com/setup/ws/1/storageUsageInfo" 

27 

28 @property 

29 def devices(self): 

30 """Returns current paired devices.""" 

31 if not self._devices: 

32 req = self.session.get(self._acc_devices_url, params=self.params) 

33 response = req.json() 

34 

35 for device_info in response["devices"]: 

36 self._devices.append(AccountDevice(device_info)) 

37 

38 return self._devices 

39 

40 @property 

41 def family(self): 

42 """Returns family members.""" 

43 if not self._family: 

44 req = self.session.get(self._acc_family_details_url, params=self.params) 

45 response = req.json() 

46 

47 for member_info in response["familyMembers"]: 

48 self._family.append( 

49 FamilyMember( 

50 member_info, 

51 self.session, 

52 self.params, 

53 self._acc_family_member_photo_url, 

54 ), 

55 ) 

56 

57 return self._family 

58 

59 @property 

60 def storage(self): 

61 """Returns storage infos.""" 

62 if not self._storage: 

63 req = self.session.get(self._acc_storage_url, params=self.params) 

64 response = req.json() 

65 

66 self._storage = AccountStorage(response) 

67 

68 return self._storage 

69 

70 def __unicode__(self): 

71 storage_available = self.storage.usage.available_storage_in_bytes 

72 return f"{{devices: {len(self.devices)}, family: {len(self.family)}, storage: {storage_available} bytes free}}" 

73 

74 def __str__(self): 

75 return self.__unicode__() 

76 

77 def __repr__(self): 

78 return f"<{type(self).__name__}: {str(self)}>" 

79 

80 

81class AccountDevice(dict): 

82 """Account device.""" 

83 

84 def __getattr__(self, key): 

85 return self[underscore_to_camelcase(key)] 

86 

87 def __unicode__(self): 

88 return f"{{model: {self.model_display_name}, name: {self.name}}}" 

89 

90 def __str__(self): 

91 return self.__unicode__() 

92 

93 def __repr__(self): 

94 return f"<{type(self).__name__}: {str(self)}>" 

95 

96 

97class FamilyMember: 

98 """A family member.""" 

99 

100 def __init__(self, member_info, session, params, acc_family_member_photo_url): 

101 self._attrs = member_info 

102 self._session = session 

103 self._params = params 

104 self._acc_family_member_photo_url = acc_family_member_photo_url 

105 

106 @property 

107 def last_name(self): 

108 """Gets the last name.""" 

109 return self._attrs.get("lastName") 

110 

111 @property 

112 def dsid(self): 

113 """Gets the dsid.""" 

114 return self._attrs.get("dsid") 

115 

116 @property 

117 def original_invitation_email(self): 

118 """Gets the original invitation.""" 

119 return self._attrs.get("originalInvitationEmail") 

120 

121 @property 

122 def full_name(self): 

123 """Gets the full name.""" 

124 return self._attrs.get("fullName") 

125 

126 @property 

127 def age_classification(self): 

128 """Gets the age classification.""" 

129 return self._attrs.get("ageClassification") 

130 

131 @property 

132 def apple_id_for_purchases(self): 

133 """Gets the apple id for purchases.""" 

134 return self._attrs.get("appleIdForPurchases") 

135 

136 @property 

137 def apple_id(self): 

138 """Gets the apple id.""" 

139 return self._attrs.get("appleId") 

140 

141 @property 

142 def family_id(self): 

143 """Gets the family id.""" 

144 return self._attrs.get("familyId") 

145 

146 @property 

147 def first_name(self): 

148 """Gets the first name.""" 

149 return self._attrs.get("firstName") 

150 

151 @property 

152 def has_parental_privileges(self): 

153 """Has parental privileges.""" 

154 return self._attrs.get("hasParentalPrivileges") 

155 

156 @property 

157 def has_screen_time_enabled(self): 

158 """Has screen time enabled.""" 

159 return self._attrs.get("hasScreenTimeEnabled") 

160 

161 @property 

162 def has_ask_to_buy_enabled(self): 

163 """Has to ask for buying.""" 

164 return self._attrs.get("hasAskToBuyEnabled") 

165 

166 @property 

167 def has_share_purchases_enabled(self): 

168 """Has share purshases.""" 

169 return self._attrs.get("hasSharePurchasesEnabled") 

170 

171 @property 

172 def share_my_location_enabled_family_members(self): 

173 """Has share my location with family.""" 

174 return self._attrs.get("shareMyLocationEnabledFamilyMembers") 

175 

176 @property 

177 def has_share_my_location_enabled(self): 

178 """Has share my location.""" 

179 return self._attrs.get("hasShareMyLocationEnabled") 

180 

181 @property 

182 def dsid_for_purchases(self): 

183 """Gets the dsid for purchases.""" 

184 return self._attrs.get("dsidForPurchases") 

185 

186 def get_photo(self): 

187 """Returns the photo.""" 

188 params_photo = dict(self._params) 

189 params_photo.update({"memberId": self.dsid}) 

190 return self._session.get( 

191 self._acc_family_member_photo_url, params=params_photo, stream=True, 

192 ) 

193 

194 def __getitem__(self, key): 

195 if self._attrs.get(key): 

196 return self._attrs[key] 

197 return getattr(self, key) 

198 

199 def __unicode__(self): 

200 return ( 

201 f"{{name: {self.full_name}, age_classification: {self.age_classification}}}" 

202 ) 

203 

204 def __str__(self): 

205 return self.__unicode__() 

206 

207 def __repr__(self): 

208 return f"<{type(self).__name__}: {str(self)}>" 

209 

210 

211class AccountStorageUsageForMedia: 

212 """Storage used for a specific media type into the account.""" 

213 

214 def __init__(self, usage_data): 

215 self.usage_data = usage_data 

216 

217 @property 

218 def key(self): 

219 """Gets the key.""" 

220 return self.usage_data["mediaKey"] 

221 

222 @property 

223 def label(self): 

224 """Gets the label.""" 

225 return self.usage_data["displayLabel"] 

226 

227 @property 

228 def color(self): 

229 """Gets the HEX color.""" 

230 return self.usage_data["displayColor"] 

231 

232 @property 

233 def usage_in_bytes(self): 

234 """Gets the usage in bytes.""" 

235 return self.usage_data["usageInBytes"] 

236 

237 def __unicode__(self): 

238 return f"{{key: {self.key}, usage: {self.usage_in_bytes} bytes}}" 

239 

240 def __str__(self): 

241 return self.__unicode__() 

242 

243 def __repr__(self): 

244 return f"<{type(self).__name__}: {str(self)}>" 

245 

246 

247class AccountStorageUsage: 

248 """Storage used for a specific media type into the account.""" 

249 

250 def __init__(self, usage_data, quota_data): 

251 self.usage_data = usage_data 

252 self.quota_data = quota_data 

253 

254 @property 

255 def comp_storage_in_bytes(self): 

256 """Gets the comp storage in bytes.""" 

257 return self.usage_data["compStorageInBytes"] 

258 

259 @property 

260 def used_storage_in_bytes(self): 

261 """Gets the used storage in bytes.""" 

262 return self.usage_data["usedStorageInBytes"] 

263 

264 @property 

265 def used_storage_in_percent(self): 

266 """Gets the used storage in percent.""" 

267 return round(self.used_storage_in_bytes * 100 / self.total_storage_in_bytes, 2) 

268 

269 @property 

270 def available_storage_in_bytes(self): 

271 """Gets the available storage in bytes.""" 

272 return self.total_storage_in_bytes - self.used_storage_in_bytes 

273 

274 @property 

275 def available_storage_in_percent(self): 

276 """Gets the available storage in percent.""" 

277 return round( 

278 self.available_storage_in_bytes * 100 / self.total_storage_in_bytes, 2, 

279 ) 

280 

281 @property 

282 def total_storage_in_bytes(self): 

283 """Gets the total storage in bytes.""" 

284 return self.usage_data["totalStorageInBytes"] 

285 

286 @property 

287 def commerce_storage_in_bytes(self): 

288 """Gets the commerce storage in bytes.""" 

289 return self.usage_data["commerceStorageInBytes"] 

290 

291 @property 

292 def quota_over(self): 

293 """Gets the over quota.""" 

294 return self.quota_data["overQuota"] 

295 

296 @property 

297 def quota_tier_max(self): 

298 """Gets the max tier quota.""" 

299 return self.quota_data["haveMaxQuotaTier"] 

300 

301 @property 

302 def quota_almost_full(self): 

303 """Gets the almost full quota.""" 

304 return self.quota_data["almost-full"] 

305 

306 @property 

307 def quota_paid(self): 

308 """Gets the paid quota.""" 

309 return self.quota_data["paidQuota"] 

310 

311 def __unicode__(self): 

312 return f"{self.used_storage_in_percent}% used of {self.total_storage_in_bytes} bytes" 

313 

314 def __str__(self): 

315 return self.__unicode__() 

316 

317 def __repr__(self): 

318 return f"<{type(self).__name__}: {str(self)}>" 

319 

320 

321class AccountStorage: 

322 """Storage of the account.""" 

323 

324 def __init__(self, storage_data): 

325 self.usage = AccountStorageUsage( 

326 storage_data.get("storageUsageInfo"), storage_data.get("quotaStatus"), 

327 ) 

328 self.usages_by_media = OrderedDict() 

329 

330 for usage_media in storage_data.get("storageUsageByMedia"): 

331 self.usages_by_media[usage_media["mediaKey"]] = AccountStorageUsageForMedia( 

332 usage_media, 

333 ) 

334 

335 def __unicode__(self): 

336 return f"{{usage: {self.usage}, usages_by_media: {self.usages_by_media}}}" 

337 

338 def __str__(self): 

339 return self.__unicode__() 

340 

341 def __repr__(self): 

342 return f"<{type(self).__name__}: {str(self)}>"