Merge pull request #1 from latestrevision/master
Move files into a format suitable for distributing on PyPI; Allow for multiple devices; Add support for downloading documents from iCloud
This commit is contained in:
commit
c5d5ca2db5
12 changed files with 426 additions and 101 deletions
93
README.md
93
README.md
|
@ -15,6 +15,36 @@ Authentication is as simple as passing your username and password to the `PyiClo
|
||||||
|
|
||||||
In the event that the username/password combination is invalid, a `PyiCloudFailedLoginException` exception is thrown.
|
In the event that the username/password combination is invalid, a `PyiCloudFailedLoginException` exception is thrown.
|
||||||
|
|
||||||
|
### Devices
|
||||||
|
|
||||||
|
You can list which devices associated with your account by using the `devices` property:
|
||||||
|
|
||||||
|
```python
|
||||||
|
>>> api.devices
|
||||||
|
{
|
||||||
|
u'i9vbKRGIcLYqJnXMd1b257kUWnoyEBcEh6yM+IfmiMLh7BmOpALS+w==': <AppleDevice(iPhone 4S: Johnny Appleseed's iPhone)>,
|
||||||
|
u'reGYDh9XwqNWTGIhNBuEwP1ds0F/Lg5t/fxNbI4V939hhXawByErk+HYVNSUzmWV': <AppleDevice(MacBook Air 11": Johnny Appleseed's MacBook Air)>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
and you can access individual devices by either their index, or their ID:
|
||||||
|
|
||||||
|
```python
|
||||||
|
>>> api.devices[0]
|
||||||
|
<AppleDevice(iPhone 4S: Johnny Appleseed's iPhone)>
|
||||||
|
>>> api.devices['i9vbKRGIcLYqJnXMd1b257kUWnoyEBcEh6yM+IfmiMLh7BmOpALS+w==']
|
||||||
|
<AppleDevice(iPhone 4S: Johnny Appleseed's iPhone)>
|
||||||
|
```
|
||||||
|
|
||||||
|
or, as a shorthand if you have only one associated apple device, you can simply use the `iphone` property to access the first device associated with your account:
|
||||||
|
|
||||||
|
```python
|
||||||
|
>>> api.iphone
|
||||||
|
<AppleDevice(iPhone 4S: Johnny Appleseed's iPhone)>
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: the first device associated with your account may not necessarily be your iPhone.
|
||||||
|
|
||||||
### Find My iPhone
|
### Find My iPhone
|
||||||
|
|
||||||
Once you have successfully authenticated, you can start querying your data!
|
Once you have successfully authenticated, you can start querying your data!
|
||||||
|
@ -78,3 +108,66 @@ from_dt = datetime(2012, 1, 1)
|
||||||
to_dt = datetime(2012, 1, 31)
|
to_dt = datetime(2012, 1, 31)
|
||||||
api.calendar.events(from_dt, to_dt)
|
api.calendar.events(from_dt, to_dt)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### File Storage (Ubiquity)
|
||||||
|
|
||||||
|
You can access documents stored in your iCloud account by using the `files` property's `dir` method:
|
||||||
|
|
||||||
|
```python
|
||||||
|
>>> api.files.dir()
|
||||||
|
[u'.do-not-delete',
|
||||||
|
u'.localized',
|
||||||
|
u'com~apple~Notes',
|
||||||
|
u'com~apple~Preview',
|
||||||
|
u'com~apple~mail',
|
||||||
|
u'com~apple~shoebox',
|
||||||
|
u'com~apple~system~spotlight'
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
You can access children and their children's children using the filename as an index:
|
||||||
|
|
||||||
|
```python
|
||||||
|
>>> api.files['com~apple~Notes']
|
||||||
|
<Folder: u'com~apple~Notes'>
|
||||||
|
>>> api.files['com~apple~Notes'].type
|
||||||
|
u'folder'
|
||||||
|
>>> api.files['com~apple~Notes'].dir()
|
||||||
|
[u'Documents']
|
||||||
|
>>> api.files['com~apple~Notes']['Documents'].dir()
|
||||||
|
[u'Some Document']
|
||||||
|
>>> api.files['com~apple~Notes']['Documents']['Some Document'].name
|
||||||
|
u'Some Document'
|
||||||
|
>>> api.files['com~apple~Notes']['Documents']['Some Document'].modified
|
||||||
|
datetime.datetime(2012, 9, 13, 2, 26, 17)
|
||||||
|
>>> api.files['com~apple~Notes']['Documents']['Some Document'].size
|
||||||
|
1308134
|
||||||
|
>>> api.files['com~apple~Notes']['Documents']['Some Document'].type
|
||||||
|
u'file'
|
||||||
|
```
|
||||||
|
|
||||||
|
And when you have a file that you'd like to download, the `open` method will return a response object from which you can read the `content`.
|
||||||
|
|
||||||
|
```python
|
||||||
|
>>> api.files['com~apple~Notes']['Documents']['Some Document'].open().content
|
||||||
|
'Hello, these are the file contents'
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: the object returned from the above `open` method is a [response object](http://www.python-requests.org/en/latest/api/#classes) and the `open` method can accept any parameters you might normally use in a request using [requests](https://github.com/kennethreitz/requests).
|
||||||
|
|
||||||
|
For example, if you know that the file you're opening has JSON content:
|
||||||
|
|
||||||
|
```python
|
||||||
|
>>> api.files['com~apple~Notes']['Documents']['information.json'].open().json()
|
||||||
|
{'How much we love you': 'lots'}
|
||||||
|
>>> api.files['com~apple~Notes']['Documents']['information.json'].open().json()['How much we love you']
|
||||||
|
'lots'
|
||||||
|
```
|
||||||
|
|
||||||
|
Or, if you're downloading a particularly large file, you may want to use the `stream` keyword argument, and read directly from the raw response object:
|
||||||
|
|
||||||
|
```python
|
||||||
|
>>> download = api.files['com~apple~Notes']['Documents']['big_file.zip'].open(stream=True)
|
||||||
|
>>> with open('downloaded_file.zip', 'wb') as opened_file:
|
||||||
|
opened_file.write(download.raw.read())
|
||||||
|
```
|
||||||
|
|
|
@ -5,7 +5,11 @@ import json
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from exceptions import PyiCloudFailedLoginException
|
from exceptions import PyiCloudFailedLoginException
|
||||||
from services import FindMyiPhoneService, CalendarService
|
from services import (
|
||||||
|
FindMyiPhoneServiceManager,
|
||||||
|
CalendarService,
|
||||||
|
UbiquityService
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class PyiCloudService(object):
|
class PyiCloudService(object):
|
||||||
|
@ -41,22 +45,10 @@ class PyiCloudService(object):
|
||||||
'User-Agent': 'Opera/9.52 (X11; Linux i686; U; en)'
|
'User-Agent': 'Opera/9.52 (X11; Linux i686; U; en)'
|
||||||
})
|
})
|
||||||
|
|
||||||
self.refresh_version()
|
self.params = {}
|
||||||
self.params = {
|
|
||||||
'clientId': self.client_id,
|
|
||||||
'clientBuildNumber': self.build_id
|
|
||||||
}
|
|
||||||
|
|
||||||
self.authenticate()
|
self.authenticate()
|
||||||
|
|
||||||
def refresh_version(self):
|
|
||||||
"""
|
|
||||||
Retrieves the buildNumber from the /version endpoint.
|
|
||||||
This is used by almost all request query strings.
|
|
||||||
"""
|
|
||||||
req = requests.get(self._base_system_url)
|
|
||||||
self.build_id = req.json()['buildNumber']
|
|
||||||
|
|
||||||
def refresh_validate(self):
|
def refresh_validate(self):
|
||||||
"""
|
"""
|
||||||
Queries the /validate endpoint and fetches two key values we need:
|
Queries the /validate endpoint and fetches two key values we need:
|
||||||
|
@ -100,11 +92,36 @@ class PyiCloudService(object):
|
||||||
self.webservices = self.discovery['webservices']
|
self.webservices = self.discovery['webservices']
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def iphone(self):
|
def devices(self):
|
||||||
|
""" Return all devices."""
|
||||||
service_root = self.webservices['findme']['url']
|
service_root = self.webservices['findme']['url']
|
||||||
return FindMyiPhoneService(service_root, self.session, self.params)
|
return FindMyiPhoneServiceManager(
|
||||||
|
service_root,
|
||||||
|
self.session,
|
||||||
|
self.params
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def iphone(self):
|
||||||
|
return self.devices[0]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def files(self):
|
||||||
|
if not hasattr(self, '_files'):
|
||||||
|
service_root = self.webservices['ubiquity']['url']
|
||||||
|
self._files = UbiquityService(service_root, self.session, self.params)
|
||||||
|
return self._files
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def calendar(self):
|
def calendar(self):
|
||||||
service_root = self.webservices['calendar']['url']
|
service_root = self.webservices['calendar']['url']
|
||||||
return CalendarService(service_root, self.session, self.params)
|
return CalendarService(service_root, self.session, self.params)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return u'iCloud API: %s' % self.user.get('apple_id')
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return unicode(self).encode('ascii', 'ignore')
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<%s>' % str(self)
|
3
pyicloud/services/__init__.py
Normal file
3
pyicloud/services/__init__.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
from calendar import CalendarService
|
||||||
|
from findmyiphone import FindMyiPhoneServiceManager
|
||||||
|
from ubiquity import UbiquityService
|
159
pyicloud/services/findmyiphone.py
Executable file
159
pyicloud/services/findmyiphone.py
Executable file
|
@ -0,0 +1,159 @@
|
||||||
|
import json
|
||||||
|
|
||||||
|
from pyicloud.exceptions import PyiCloudNoDevicesException
|
||||||
|
|
||||||
|
|
||||||
|
class FindMyiPhoneServiceManager(object):
|
||||||
|
""" The 'Find my iPhone' iCloud service
|
||||||
|
|
||||||
|
This connects to iCloud and return phone data including the near-realtime
|
||||||
|
latitude and longitude.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, service_root, session, params):
|
||||||
|
self.session = session
|
||||||
|
self.params = params
|
||||||
|
self._service_root = service_root
|
||||||
|
self._fmip_endpoint = '%s/fmipservice/client/web' % self._service_root
|
||||||
|
self._fmip_refresh_url = '%s/refreshClient' % self._fmip_endpoint
|
||||||
|
self._fmip_sound_url = '%s/playSound' % self._fmip_endpoint
|
||||||
|
self._fmip_lost_url = '%s/lostDevice' % self._fmip_endpoint
|
||||||
|
|
||||||
|
self._devices = {}
|
||||||
|
self.refresh_client()
|
||||||
|
|
||||||
|
def refresh_client(self):
|
||||||
|
""" Refreshes the FindMyiPhoneService endpoint,
|
||||||
|
|
||||||
|
This ensures that the location data is up-to-date.
|
||||||
|
|
||||||
|
"""
|
||||||
|
host = self._service_root.split('//')[1].split(':')[0]
|
||||||
|
self.session.headers.update({'host': host})
|
||||||
|
req = self.session.post(self._fmip_refresh_url, params=self.params)
|
||||||
|
self.response = req.json()
|
||||||
|
|
||||||
|
for device_info in self.response['content']:
|
||||||
|
device_id = device_info['id']
|
||||||
|
if not device_id in self._devices:
|
||||||
|
self._devices[device_id] = AppleDevice(
|
||||||
|
device_info,
|
||||||
|
self.session,
|
||||||
|
self.params,
|
||||||
|
manager=self,
|
||||||
|
sound_url=self._fmip_sound_url,
|
||||||
|
lost_url=self._fmip_lost_url
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self._devices[device_id].update(device_info)
|
||||||
|
|
||||||
|
if not self._devices:
|
||||||
|
raise PyiCloudNoDevicesException(message)
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
if isinstance(key, int):
|
||||||
|
key = self.keys()[key]
|
||||||
|
return self._devices[key]
|
||||||
|
|
||||||
|
def __getattr__(self, attr):
|
||||||
|
return getattr(self._devices, attr)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return unicode(self._devices)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return unicode(self).encode('ascii', 'ignore')
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return str(self)
|
||||||
|
|
||||||
|
|
||||||
|
class AppleDevice(object):
|
||||||
|
def __init__(self, content, session, params, manager,
|
||||||
|
sound_url=None, lost_url=None):
|
||||||
|
self.content = content
|
||||||
|
self.manager = manager
|
||||||
|
self.session = session
|
||||||
|
self.params = params
|
||||||
|
|
||||||
|
self.sound_url = sound_url
|
||||||
|
self.lost_url = lost_url
|
||||||
|
|
||||||
|
def update(self, data):
|
||||||
|
self.content = data
|
||||||
|
|
||||||
|
def location(self):
|
||||||
|
self.manager.refresh_client()
|
||||||
|
return self.content['location']
|
||||||
|
|
||||||
|
def status(self, additional=[]):
|
||||||
|
""" Returns status information for device.
|
||||||
|
|
||||||
|
This returns only a subset of possible properties.
|
||||||
|
"""
|
||||||
|
self.manager.refresh_client()
|
||||||
|
fields = ['batteryLevel', 'deviceDisplayName', 'deviceStatus', 'name']
|
||||||
|
fields += additional
|
||||||
|
properties = {}
|
||||||
|
for field in fields:
|
||||||
|
properties[field] = self.content.get(field)
|
||||||
|
return properties
|
||||||
|
|
||||||
|
def play_sound(self, subject='Find My iPhone Alert'):
|
||||||
|
""" Send a request to the device to play a sound.
|
||||||
|
|
||||||
|
It's possible to pass a custom message by changing the `subject`.
|
||||||
|
"""
|
||||||
|
data = json.dumps({'device': self.content['id'], 'subject': subject})
|
||||||
|
self.session.post(
|
||||||
|
self.sound_url,
|
||||||
|
params=self.params,
|
||||||
|
data=data
|
||||||
|
)
|
||||||
|
|
||||||
|
def lost_device(self, number,
|
||||||
|
text='This iPhone has been lost. Please call me.'):
|
||||||
|
""" Send a request to the device to trigger 'lost mode'.
|
||||||
|
|
||||||
|
The device will show the message in `text`, and if a number has
|
||||||
|
been passed, then the person holding the device can call
|
||||||
|
the number without entering the passcode.
|
||||||
|
"""
|
||||||
|
data = json.dumps({
|
||||||
|
'text': text,
|
||||||
|
'userText': True,
|
||||||
|
'ownerNbr': number,
|
||||||
|
'lostModeEnabled': True,
|
||||||
|
'trackingEnabled': True,
|
||||||
|
'device': self.content['id'],
|
||||||
|
})
|
||||||
|
self.session.post(
|
||||||
|
self.lost_url,
|
||||||
|
params=self.params,
|
||||||
|
data=data
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def data(self):
|
||||||
|
return self.content
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
return self.content[key]
|
||||||
|
|
||||||
|
def __getattr__(self, attr):
|
||||||
|
return getattr(self.content, attr)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
display_name = self['deviceDisplayName']
|
||||||
|
name = self['name']
|
||||||
|
return u'%s: %s' % (
|
||||||
|
display_name,
|
||||||
|
name,
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return unicode(self).encode('ascii', 'ignore')
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<AppleDevice(%s)>' % str(self)
|
117
pyicloud/services/ubiquity.py
Normal file
117
pyicloud/services/ubiquity.py
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
|
class UbiquityService(object):
|
||||||
|
""" The 'Ubiquity' iCloud service."""
|
||||||
|
|
||||||
|
def __init__(self, service_root, session, params):
|
||||||
|
self.session = session
|
||||||
|
self.params = params
|
||||||
|
self._root = None
|
||||||
|
|
||||||
|
self._service_root = service_root
|
||||||
|
self._node_url = '/ws/%s/%s/%s'
|
||||||
|
|
||||||
|
host = self._service_root.split('//')[1].split(':')[0]
|
||||||
|
self.session.headers.update({'host': host})
|
||||||
|
|
||||||
|
def get_node_url(self, id, variant='item'):
|
||||||
|
return self._service_root + self._node_url % (
|
||||||
|
self.params['dsid'],
|
||||||
|
variant,
|
||||||
|
id
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_node(self, id):
|
||||||
|
request = self.session.get(self.get_node_url(id))
|
||||||
|
return UbiquityNode(self, request.json())
|
||||||
|
|
||||||
|
def get_children(self, id):
|
||||||
|
request = self.session.get(
|
||||||
|
self.get_node_url(id, 'parent')
|
||||||
|
)
|
||||||
|
items = request.json()['item_list']
|
||||||
|
return [UbiquityNode(self, item) for item in items]
|
||||||
|
|
||||||
|
def get_file(self, id, **kwargs):
|
||||||
|
request = self.session.get(
|
||||||
|
self.get_node_url(id, 'file'),
|
||||||
|
**kwargs
|
||||||
|
)
|
||||||
|
return request
|
||||||
|
|
||||||
|
@property
|
||||||
|
def root(self):
|
||||||
|
if not self._root:
|
||||||
|
self._root = self.get_node(0)
|
||||||
|
return self._root
|
||||||
|
|
||||||
|
def __getattr__(self, attr):
|
||||||
|
return getattr(self.root, attr)
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
return self.root[key]
|
||||||
|
|
||||||
|
|
||||||
|
class UbiquityNode(object):
|
||||||
|
def __init__(self, conn, data):
|
||||||
|
self.data = data
|
||||||
|
self.connection = conn
|
||||||
|
|
||||||
|
@property
|
||||||
|
def item_id(self):
|
||||||
|
return self.data.get('item_id')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
return self.data.get('name')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def type(self):
|
||||||
|
return self.data.get('type')
|
||||||
|
|
||||||
|
def get_children(self):
|
||||||
|
if not hasattr(self, '_children'):
|
||||||
|
self._children = self.connection.get_children(self.item_id)
|
||||||
|
return self._children
|
||||||
|
|
||||||
|
@property
|
||||||
|
def size(self):
|
||||||
|
try:
|
||||||
|
return int(self.data.get('size'))
|
||||||
|
except ValueError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def modified(self):
|
||||||
|
return datetime.strptime(
|
||||||
|
self.data.get('modified'),
|
||||||
|
'%Y-%m-%dT%H:%M:%SZ'
|
||||||
|
)
|
||||||
|
|
||||||
|
def dir(self):
|
||||||
|
return [child.name for child in self.get_children()]
|
||||||
|
|
||||||
|
def open(self, **kwargs):
|
||||||
|
return self.connection.get_file(self.item_id, **kwargs)
|
||||||
|
|
||||||
|
def get(self, name):
|
||||||
|
return [child for child in self.get_children() if child.name == name][0]
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
try:
|
||||||
|
return self.get(key)
|
||||||
|
except IndexError:
|
||||||
|
raise KeyError('No child named %s exists' % key)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name.encode('unicode-escape')
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<%s: u'%s'>" % (
|
||||||
|
self.type.capitalize(),
|
||||||
|
str(self)
|
||||||
|
)
|
|
@ -1,2 +1 @@
|
||||||
uuid
|
requests>=1.2
|
||||||
requests
|
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
from calendar import CalendarService
|
|
||||||
from findmyiphone import FindMyiPhoneService
|
|
|
@ -1,81 +0,0 @@
|
||||||
import json
|
|
||||||
|
|
||||||
from pyicloud.exceptions import PyiCloudNoDevicesException
|
|
||||||
|
|
||||||
|
|
||||||
class FindMyiPhoneService(object):
|
|
||||||
"""
|
|
||||||
The 'Find my iPhone' iCloud service, connects to iCloud and returns
|
|
||||||
phone data including the near-realtime latitude and longitude.
|
|
||||||
"""
|
|
||||||
def __init__(self, service_root, session, params):
|
|
||||||
self.session = session
|
|
||||||
self.params = params
|
|
||||||
self._service_root = service_root
|
|
||||||
self._fmip_endpoint = '%s/fmipservice/client/web' % self._service_root
|
|
||||||
self._fmip_refresh_url = '%s/refreshClient' % self._fmip_endpoint
|
|
||||||
self._fmip_sound_url = '%s/playSound' % self._fmip_endpoint
|
|
||||||
self._fmip_lost_url = '%s/lostDevice' % self._fmip_endpoint
|
|
||||||
|
|
||||||
def refresh_client(self):
|
|
||||||
"""
|
|
||||||
Refreshes the FindMyiPhoneService endpoint,
|
|
||||||
ensuring that the location data is up-to-date.
|
|
||||||
"""
|
|
||||||
host = self._service_root.split('//')[1].split(':')[0]
|
|
||||||
self.session.headers.update({'host': host})
|
|
||||||
req = self.session.post(self._fmip_refresh_url, params=self.params)
|
|
||||||
self.response = req.json()
|
|
||||||
if self.response['content']:
|
|
||||||
# TODO: Support multiple devices.
|
|
||||||
self.content = self.response['content'][0]
|
|
||||||
else:
|
|
||||||
message = 'You do not have any active devices.'
|
|
||||||
raise PyiCloudNoDevicesException(message)
|
|
||||||
self.user_info = self.response['userInfo']
|
|
||||||
|
|
||||||
def location(self):
|
|
||||||
self.refresh_client()
|
|
||||||
return self.content['location']
|
|
||||||
|
|
||||||
def status(self, additional=[]):
|
|
||||||
"""
|
|
||||||
The FindMyiPhoneService response is quite bloated, this method
|
|
||||||
will return a subset of the more useful properties.
|
|
||||||
"""
|
|
||||||
self.refresh_client()
|
|
||||||
fields = ['batteryLevel', 'deviceDisplayName', 'deviceStatus', 'name']
|
|
||||||
fields += additional
|
|
||||||
properties = {}
|
|
||||||
for field in fields:
|
|
||||||
properties[field] = self.content.get(field, 'Unknown')
|
|
||||||
return properties
|
|
||||||
|
|
||||||
def play_sound(self, subject='Find My iPhone Alert'):
|
|
||||||
"""
|
|
||||||
Send a request to the device to play a sound, it's possible to
|
|
||||||
pass a custom message by changing the `subject`.
|
|
||||||
"""
|
|
||||||
self.refresh_client()
|
|
||||||
data = json.dumps({'device': self.content['id'], 'subject': subject})
|
|
||||||
self.session.post(self._fmip_sound_url, params=self.params, data=data)
|
|
||||||
|
|
||||||
def lost_device(self, number, text=None):
|
|
||||||
"""
|
|
||||||
Send a request to the device to trigger 'lost mode'. The
|
|
||||||
device will show the message in `text`, and if a number has
|
|
||||||
been passed, then the person holding the device can call
|
|
||||||
the number without entering the passcode.
|
|
||||||
"""
|
|
||||||
self.refresh_client()
|
|
||||||
if not text:
|
|
||||||
text = 'This iPhone has been lost. Please call me.'
|
|
||||||
data = json.dumps({
|
|
||||||
'text': text,
|
|
||||||
'userText': True,
|
|
||||||
'ownerNbr': number,
|
|
||||||
'lostModeEnabled': True,
|
|
||||||
'trackingEnabled': True,
|
|
||||||
'device': self.content['id'],
|
|
||||||
})
|
|
||||||
self.session.post(self._fmip_lost_url, params=self.params, data=data)
|
|
20
setup.py
Normal file
20
setup.py
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
|
|
||||||
|
with open('requirements.txt') as f:
|
||||||
|
required = f.read().splitlines()
|
||||||
|
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name='pyicloud',
|
||||||
|
version='0.2',
|
||||||
|
url='https://github.com/picklepete/pyicloud',
|
||||||
|
description=(
|
||||||
|
'PyiCloud is a module which allows pythonistas to '
|
||||||
|
'interact with iCloud webservices.'
|
||||||
|
),
|
||||||
|
author='Peter Evans',
|
||||||
|
author_email='evans.peter@gmail.com',
|
||||||
|
packages=find_packages(),
|
||||||
|
install_requires=required
|
||||||
|
)
|
Loading…
Reference in a new issue