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

217 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2024-04-12 14:26 +0000

1"""Account service.""" 

2from collections import OrderedDict 

3 

4from six import PY2 

5 

6from icloudpy.utils import underscore_to_camelcase 

7 

8 

9class AccountService: 

10 """The 'Account' iCloud service.""" 

11 

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

13 self.session = session 

14 self.params = params 

15 self._service_root = service_root 

16 

17 self._devices = [] 

18 self._family = [] 

19 self._storage = None 

20 

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

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

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

24 self._acc_family_member_photo_url = ( 

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

26 ) 

27 

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

29 

30 @property 

31 def devices(self): 

32 """Returns current paired devices.""" 

33 if not self._devices: 

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

35 response = req.json() 

36 

37 for device_info in response["devices"]: 

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

39 

40 return self._devices 

41 

42 @property 

43 def family(self): 

44 """Returns family members.""" 

45 if not self._family: 

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

47 response = req.json() 

48 

49 for member_info in response["familyMembers"]: 

50 self._family.append( 

51 FamilyMember( 

52 member_info, 

53 self.session, 

54 self.params, 

55 self._acc_family_member_photo_url, 

56 ) 

57 ) 

58 

59 return self._family 

60 

61 @property 

62 def storage(self): 

63 """Returns storage infos.""" 

64 if not self._storage: 

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

66 response = req.json() 

67 

68 self._storage = AccountStorage(response) 

69 

70 return self._storage 

71 

72 def __unicode__(self): 

73 storage_available = self.storage.usage.available_storage_in_bytes 

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

75 

76 def __str__(self): 

77 as_unicode = self.__unicode__() 

78 if PY2: 

79 return as_unicode.encode("utf-8", "ignore") 

80 return as_unicode 

81 

82 def __repr__(self): 

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

84 

85 

86class AccountDevice(dict): 

87 """Account device.""" 

88 

89 def __getattr__(self, key): 

90 return self[underscore_to_camelcase(key)] 

91 

92 def __unicode__(self): 

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

94 

95 def __str__(self): 

96 as_unicode = self.__unicode__() 

97 if PY2: 

98 return as_unicode.encode("utf-8", "ignore") 

99 return as_unicode 

100 

101 def __repr__(self): 

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

103 

104 

105class FamilyMember: 

106 """A family member.""" 

107 

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

109 self._attrs = member_info 

110 self._session = session 

111 self._params = params 

112 self._acc_family_member_photo_url = acc_family_member_photo_url 

113 

114 @property 

115 def last_name(self): 

116 """Gets the last name.""" 

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

118 

119 @property 

120 def dsid(self): 

121 """Gets the dsid.""" 

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

123 

124 @property 

125 def original_invitation_email(self): 

126 """Gets the original invitation.""" 

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

128 

129 @property 

130 def full_name(self): 

131 """Gets the full name.""" 

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

133 

134 @property 

135 def age_classification(self): 

136 """Gets the age classification.""" 

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

138 

139 @property 

140 def apple_id_for_purchases(self): 

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

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

143 

144 @property 

145 def apple_id(self): 

146 """Gets the apple id.""" 

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

148 

149 @property 

150 def family_id(self): 

151 """Gets the family id.""" 

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

153 

154 @property 

155 def first_name(self): 

156 """Gets the first name.""" 

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

158 

159 @property 

160 def has_parental_privileges(self): 

161 """Has parental privileges.""" 

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

163 

164 @property 

165 def has_screen_time_enabled(self): 

166 """Has screen time enabled.""" 

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

168 

169 @property 

170 def has_ask_to_buy_enabled(self): 

171 """Has to ask for buying.""" 

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

173 

174 @property 

175 def has_share_purchases_enabled(self): 

176 """Has share purshases.""" 

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

178 

179 @property 

180 def share_my_location_enabled_family_members(self): 

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

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

183 

184 @property 

185 def has_share_my_location_enabled(self): 

186 """Has share my location.""" 

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

188 

189 @property 

190 def dsid_for_purchases(self): 

191 """Gets the dsid for purchases.""" 

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

193 

194 def get_photo(self): 

195 """Returns the photo.""" 

196 params_photo = dict(self._params) 

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

198 return self._session.get( 

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

200 ) 

201 

202 def __getitem__(self, key): 

203 if self._attrs.get(key): 

204 return self._attrs[key] 

205 return getattr(self, key) 

206 

207 def __unicode__(self): 

208 return ( 

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

210 ) 

211 

212 def __str__(self): 

213 as_unicode = self.__unicode__() 

214 if PY2: 

215 return as_unicode.encode("utf-8", "ignore") 

216 return as_unicode 

217 

218 def __repr__(self): 

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

220 

221 

222class AccountStorageUsageForMedia: 

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

224 

225 def __init__(self, usage_data): 

226 self.usage_data = usage_data 

227 

228 @property 

229 def key(self): 

230 """Gets the key.""" 

231 return self.usage_data["mediaKey"] 

232 

233 @property 

234 def label(self): 

235 """Gets the label.""" 

236 return self.usage_data["displayLabel"] 

237 

238 @property 

239 def color(self): 

240 """Gets the HEX color.""" 

241 return self.usage_data["displayColor"] 

242 

243 @property 

244 def usage_in_bytes(self): 

245 """Gets the usage in bytes.""" 

246 return self.usage_data["usageInBytes"] 

247 

248 def __unicode__(self): 

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

250 

251 def __str__(self): 

252 as_unicode = self.__unicode__() 

253 if PY2: 

254 return as_unicode.encode("utf-8", "ignore") 

255 return as_unicode 

256 

257 def __repr__(self): 

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

259 

260 

261class AccountStorageUsage: 

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

263 

264 def __init__(self, usage_data, quota_data): 

265 self.usage_data = usage_data 

266 self.quota_data = quota_data 

267 

268 @property 

269 def comp_storage_in_bytes(self): 

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

271 return self.usage_data["compStorageInBytes"] 

272 

273 @property 

274 def used_storage_in_bytes(self): 

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

276 return self.usage_data["usedStorageInBytes"] 

277 

278 @property 

279 def used_storage_in_percent(self): 

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

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

282 

283 @property 

284 def available_storage_in_bytes(self): 

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

286 return self.total_storage_in_bytes - self.used_storage_in_bytes 

287 

288 @property 

289 def available_storage_in_percent(self): 

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

291 return round( 

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

293 ) 

294 

295 @property 

296 def total_storage_in_bytes(self): 

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

298 return self.usage_data["totalStorageInBytes"] 

299 

300 @property 

301 def commerce_storage_in_bytes(self): 

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

303 return self.usage_data["commerceStorageInBytes"] 

304 

305 @property 

306 def quota_over(self): 

307 """Gets the over quota.""" 

308 return self.quota_data["overQuota"] 

309 

310 @property 

311 def quota_tier_max(self): 

312 """Gets the max tier quota.""" 

313 return self.quota_data["haveMaxQuotaTier"] 

314 

315 @property 

316 def quota_almost_full(self): 

317 """Gets the almost full quota.""" 

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

319 

320 @property 

321 def quota_paid(self): 

322 """Gets the paid quota.""" 

323 return self.quota_data["paidQuota"] 

324 

325 def __unicode__(self): 

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

327 

328 def __str__(self): 

329 as_unicode = self.__unicode__() 

330 if PY2: 

331 return as_unicode.encode("utf-8", "ignore") 

332 return as_unicode 

333 

334 def __repr__(self): 

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

336 

337 

338class AccountStorage: 

339 """Storage of the account.""" 

340 

341 def __init__(self, storage_data): 

342 self.usage = AccountStorageUsage( 

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

344 ) 

345 self.usages_by_media = OrderedDict() 

346 

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

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

349 usage_media 

350 ) 

351 

352 def __unicode__(self): 

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

354 

355 def __str__(self): 

356 as_unicode = self.__unicode__() 

357 if PY2: 

358 return as_unicode.encode("utf-8", "ignore") 

359 return as_unicode 

360 

361 def __repr__(self): 

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