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

198 statements  

« prev     ^ index     » next       coverage.py v7.17.0, created at 2026-10-28 23:44 +0000

1"""Account service.""" 

2 

3from collections import OrderedDict 

4 

5from icloudpy.utils import underscore_to_camelcase 

6 

7 

8class AccountService: 

9 """The 'Account' iCloud service.""" 

10 

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

12 self.session = session 

13 self.params = params 

14 self._service_root = service_root 

15 

16 self._devices = [] 

17 self._family = [] 

18 self._storage = None 

19 

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

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

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

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

24 

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

26 

27 @property 

28 def devices(self): 

29 """Returns current paired devices.""" 

30 if not self._devices: 

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

32 response = req.json() 

33 

34 for device_info in response["devices"]: 

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

36 

37 return self._devices 

38 

39 @property 

40 def family(self): 

41 """Returns family members.""" 

42 if not self._family: 

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

44 response = req.json() 

45 

46 for member_info in response["familyMembers"]: 

47 self._family.append( 

48 FamilyMember( 

49 member_info, 

50 self.session, 

51 self.params, 

52 self._acc_family_member_photo_url, 

53 ), 

54 ) 

55 

56 return self._family 

57 

58 @property 

59 def storage(self): 

60 """Returns storage infos.""" 

61 if not self._storage: 

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

63 response = req.json() 

64 

65 self._storage = AccountStorage(response) 

66 

67 return self._storage 

68 

69 def __unicode__(self): 

70 storage_available = self.storage.usage.available_storage_in_bytes 

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

72 

73 def __str__(self): 

74 return self.__unicode__() 

75 

76 def __repr__(self): 

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

78 

79 

80class AccountDevice(dict): 

81 """Account device.""" 

82 

83 def __getattr__(self, key): 

84 return self[underscore_to_camelcase(key)] 

85 

86 def __unicode__(self): 

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

88 

89 def __str__(self): 

90 return self.__unicode__() 

91 

92 def __repr__(self): 

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

94 

95 

96class FamilyMember: 

97 """A family member.""" 

98 

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

100 self._attrs = member_info 

101 self._session = session 

102 self._params = params 

103 self._acc_family_member_photo_url = acc_family_member_photo_url 

104 

105 @property 

106 def last_name(self): 

107 """Gets the last name.""" 

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

109 

110 @property 

111 def dsid(self): 

112 """Gets the dsid.""" 

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

114 

115 @property 

116 def original_invitation_email(self): 

117 """Gets the original invitation.""" 

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

119 

120 @property 

121 def full_name(self): 

122 """Gets the full name.""" 

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

124 

125 @property 

126 def age_classification(self): 

127 """Gets the age classification.""" 

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

129 

130 @property 

131 def apple_id_for_purchases(self): 

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

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

134 

135 @property 

136 def apple_id(self): 

137 """Gets the apple id.""" 

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

139 

140 @property 

141 def family_id(self): 

142 """Gets the family id.""" 

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

144 

145 @property 

146 def first_name(self): 

147 """Gets the first name.""" 

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

149 

150 @property 

151 def has_parental_privileges(self): 

152 """Has parental privileges.""" 

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

154 

155 @property 

156 def has_screen_time_enabled(self): 

157 """Has screen time enabled.""" 

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

159 

160 @property 

161 def has_ask_to_buy_enabled(self): 

162 """Has to ask for buying.""" 

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

164 

165 @property 

166 def has_share_purchases_enabled(self): 

167 """Has share purshases.""" 

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

169 

170 @property 

171 def share_my_location_enabled_family_members(self): 

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

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

174 

175 @property 

176 def has_share_my_location_enabled(self): 

177 """Has share my location.""" 

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

179 

180 @property 

181 def dsid_for_purchases(self): 

182 """Gets the dsid for purchases.""" 

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

184 

185 def get_photo(self): 

186 """Returns the photo.""" 

187 params_photo = dict(self._params) 

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

189 return self._session.get( 

190 self._acc_family_member_photo_url, 

191 params=params_photo, 

192 stream=True, 

193 ) 

194 

195 def __getitem__(self, key): 

196 if self._attrs.get(key): 

197 return self._attrs[key] 

198 return getattr(self, key) 

199 

200 def __unicode__(self): 

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

202 

203 def __str__(self): 

204 return self.__unicode__() 

205 

206 def __repr__(self): 

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

208 

209 

210class AccountStorageUsageForMedia: 

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

212 

213 def __init__(self, usage_data): 

214 self.usage_data = usage_data 

215 

216 @property 

217 def key(self): 

218 """Gets the key.""" 

219 return self.usage_data["mediaKey"] 

220 

221 @property 

222 def label(self): 

223 """Gets the label.""" 

224 return self.usage_data["displayLabel"] 

225 

226 @property 

227 def color(self): 

228 """Gets the HEX color.""" 

229 return self.usage_data["displayColor"] 

230 

231 @property 

232 def usage_in_bytes(self): 

233 """Gets the usage in bytes.""" 

234 return self.usage_data["usageInBytes"] 

235 

236 def __unicode__(self): 

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

238 

239 def __str__(self): 

240 return self.__unicode__() 

241 

242 def __repr__(self): 

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

244 

245 

246class AccountStorageUsage: 

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

248 

249 def __init__(self, usage_data, quota_data): 

250 self.usage_data = usage_data 

251 self.quota_data = quota_data 

252 

253 @property 

254 def comp_storage_in_bytes(self): 

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

256 return self.usage_data["compStorageInBytes"] 

257 

258 @property 

259 def used_storage_in_bytes(self): 

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

261 return self.usage_data["usedStorageInBytes"] 

262 

263 @property 

264 def used_storage_in_percent(self): 

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

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

267 

268 @property 

269 def available_storage_in_bytes(self): 

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

271 return self.total_storage_in_bytes - self.used_storage_in_bytes 

272 

273 @property 

274 def available_storage_in_percent(self): 

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

276 return round( 

277 self.available_storage_in_bytes * 100 / self.total_storage_in_bytes, 

278 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"), 

327 storage_data.get("quotaStatus"), 

328 ) 

329 self.usages_by_media = OrderedDict() 

330 

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

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

333 usage_media, 

334 ) 

335 

336 def __unicode__(self): 

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

338 

339 def __str__(self): 

340 return self.__unicode__() 

341 

342 def __repr__(self): 

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