Coverage for icloudpy/services/findmyiphone.py: 63%
89 statements
« prev ^ index » next coverage.py v6.5.0, created at 2024-04-12 14:26 +0000
« prev ^ index » next coverage.py v6.5.0, created at 2024-04-12 14:26 +0000
1"""Find my iPhone service."""
2import json
4from six import PY2
6from icloudpy.exceptions import ICloudPyNoDevicesException
9class FindMyiPhoneServiceManager:
10 """The 'Find my iPhone' iCloud service
12 This connects to iCloud and return phone data including the near-realtime
13 latitude and longitude.
14 """
16 def __init__(self, service_root, session, params, with_family=False):
17 self.session = session
18 self.params = params
19 self.with_family = with_family
21 fmip_endpoint = f"{service_root}/fmipservice/client/web"
22 self._fmip_refresh_url = f"{fmip_endpoint}/refreshClient"
23 self._fmip_sound_url = f"{fmip_endpoint}/playSound"
24 self._fmip_message_url = f"{fmip_endpoint}/sendMessage"
25 self._fmip_lost_url = f"{fmip_endpoint}/lostDevice"
27 self._devices = {}
28 self.refresh_client()
30 def refresh_client(self):
31 """Refreshes the FindMyiPhoneService endpoint,
33 This ensures that the location data is up-to-date.
35 """
36 req = self.session.post(
37 self._fmip_refresh_url,
38 params=self.params,
39 data=json.dumps(
40 {
41 "clientContext": {
42 "fmly": self.with_family,
43 "shouldLocate": True,
44 "selectedDevice": "all",
45 "deviceListVersion": 1,
46 }
47 }
48 ),
49 )
50 self.response = req.json()
52 for device_info in self.response["content"]:
53 device_id = device_info["id"]
54 if device_id not in self._devices:
55 self._devices[device_id] = AppleDevice(
56 device_info,
57 self.session,
58 self.params,
59 manager=self,
60 sound_url=self._fmip_sound_url,
61 lost_url=self._fmip_lost_url,
62 message_url=self._fmip_message_url,
63 )
64 else:
65 self._devices[device_id].update(device_info)
67 if not self._devices:
68 raise ICloudPyNoDevicesException()
70 def __getitem__(self, key):
71 if isinstance(key, int):
72 key = list(self.keys())[key]
73 return self._devices[key]
75 def __getattr__(self, attr):
76 return getattr(self._devices, attr)
78 def __unicode__(self):
79 return str(self._devices)
81 def __str__(self):
82 as_unicode = self.__unicode__()
83 if PY2:
84 return as_unicode.encode("utf-8", "ignore")
85 return as_unicode
87 def __repr__(self):
88 return str(self)
91class AppleDevice:
92 """Apple device."""
94 def __init__(
95 self,
96 content,
97 session,
98 params,
99 manager,
100 sound_url=None,
101 lost_url=None,
102 message_url=None,
103 ):
104 self.content = content
105 self.manager = manager
106 self.session = session
107 self.params = params
109 self.sound_url = sound_url
110 self.lost_url = lost_url
111 self.message_url = message_url
113 def update(self, data):
114 """Updates the device data."""
115 self.content = data
117 def location(self):
118 """Updates the device location."""
119 self.manager.refresh_client()
120 return self.content["location"]
122 def status(self, additional=[]): # pylint: disable=dangerous-default-value
123 """Returns status information for device.
125 This returns only a subset of possible properties.
126 """
127 self.manager.refresh_client()
128 fields = ["batteryLevel", "deviceDisplayName", "deviceStatus", "name"]
129 fields += additional
130 properties = {}
131 for field in fields:
132 properties[field] = self.content.get(field)
133 return properties
135 def play_sound(self, subject="Find My iPhone Alert"):
136 """Send a request to the device to play a sound.
138 It's possible to pass a custom message by changing the `subject`.
139 """
140 data = json.dumps(
141 {
142 "device": self.content["id"],
143 "subject": subject,
144 "clientContext": {"fmly": True},
145 }
146 )
147 self.session.post(self.sound_url, params=self.params, data=data)
149 def display_message(
150 self, subject="Find My iPhone Alert", message="This is a note", sounds=False
151 ):
152 """Send a request to the device to play a sound.
154 It's possible to pass a custom message by changing the `subject`.
155 """
156 data = json.dumps(
157 {
158 "device": self.content["id"],
159 "subject": subject,
160 "sound": sounds,
161 "userText": True,
162 "text": message,
163 }
164 )
165 self.session.post(self.message_url, params=self.params, data=data)
167 def lost_device(
168 self, number, text="This iPhone has been lost. Please call me.", newpasscode=""
169 ):
170 """Send a request to the device to trigger 'lost mode'.
172 The device will show the message in `text`, and if a number has
173 been passed, then the person holding the device can call
174 the number without entering the passcode.
175 """
176 data = json.dumps(
177 {
178 "text": text,
179 "userText": True,
180 "ownerNbr": number,
181 "lostModeEnabled": True,
182 "trackingEnabled": True,
183 "device": self.content["id"],
184 "passcode": newpasscode,
185 }
186 )
187 self.session.post(self.lost_url, params=self.params, data=data)
189 @property
190 def data(self):
191 """Gets the device data."""
192 return self.content
194 def __getitem__(self, key):
195 return self.content[key]
197 def __getattr__(self, attr):
198 return getattr(self.content, attr)
200 def __unicode__(self):
201 display_name = self["deviceDisplayName"]
202 name = self["name"]
203 return f"{display_name}: {name}"
205 def __str__(self):
206 as_unicode = self.__unicode__()
207 if PY2:
208 return as_unicode.encode("utf-8", "ignore")
209 return as_unicode
211 def __repr__(self):
212 return f"<AppleDevice({self})>"