2f0dcd1ac2
Squashed commit of the following: commit 0eb23aa87c264152716933e03827f040742e6d70 Author: Adam Coddington <me@adamcoddington.net> Date: Sat Feb 20 14:21:48 2016 -0800 Updating readme to reflect updated flow. commit 840268e2db6093b5cb573c6a3e71204bf5b08b48 Author: Adam Coddington <me@adamcoddington.net> Date: Sat Feb 20 14:18:39 2016 -0800 Dropping python 2.6 support workaround. commit 9dcbd460482c2925bda490be2be884a2a2526062 Author: Adam Coddington <me@adamcoddington.net> Date: Sat Feb 20 14:18:00 2016 -0800 Adding additional behavior at @torarnv's request. commit 6c711bb12beea7c792b5d386203373423b6e56e2 Author: Adam Coddington <me@adamcoddington.net> Date: Sat Jan 23 15:08:29 2016 -0800 Workaround for obsolete versions of Python 2. commit b0765b7b6bf9974348061043da9a110c6bd7d985 Author: Adam Coddington <me@adamcoddington.net> Date: Sat Jan 23 14:56:53 2016 -0800 Style changes to avoid line length overage. commit 4decc576432ef23edae01b9621f2689b4f3c6c84 Author: Adam Coddington <me@adamcoddington.net> Date: Sat Jan 23 14:01:27 2016 -0800 Adding documentation; also adding --delete-from-keyring command-line option. commit a6b0224e93a8bc9159cf06ba5792a384f7fbb060 Author: Adam Coddington <me@adamcoddington.net> Date: Sat Jan 23 13:44:09 2016 -0800 Adding functionality allowing authentication using iCloud passwords stored in the system keychain. Adds the following new command-line options: * `--password-interactive`: Allows you to specify your password interactively rather than typing it into the command-line. * `--store-in-keychain`: Allows you to store the password in use in the system keychain. If no password is specified when instantiating `PyiCloudService` or when using the command-line utility (via either `--password-interactive` or `--password`), the system keychain will be queried for a stored password, and an exception will be raised if one was not found. commit 4ba03fb02d51673dfb7183dde49ab4c0bec4afb3 Author: Adam Coddington <me@adamcoddington.net> Date: Sat Jan 23 13:43:39 2016 -0800 Removing unused imports.
200 lines
7.5 KiB
ReStructuredText
200 lines
7.5 KiB
ReStructuredText
.. image:: https://travis-ci.org/picklepete/pyicloud.svg?branch=master
|
|
:alt: Check out our test status at https://travis-ci.org/picklepete/pyicloud
|
|
:target: https://travis-ci.org/picklepete/pyicloud
|
|
|
|
.. image:: https://badges.gitter.im/Join%20Chat.svg
|
|
:alt: Join the chat at https://gitter.im/picklepete/pyicloud
|
|
:target: https://gitter.im/picklepete/pyicloud?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
|
|
|
|
|
|
PyiCloud is a module which allows pythonistas to interact with iCloud webservices. It's powered by the fantastic `requests <https://github.com/kennethreitz/requests>`_ HTTP library.
|
|
|
|
At its core, PyiCloud connects to iCloud using your username and password, then performs calendar and iPhone queries against their API.
|
|
|
|
==============
|
|
Authentication
|
|
==============
|
|
|
|
Authentication without using a saved password is as simple as passing your username and password to the ``PyiCloudService`` class:
|
|
|
|
>>> from pyicloud import PyiCloudService
|
|
>>> api = PyiCloudService('jappleseed@apple.com', 'password')
|
|
|
|
In the event that the username/password combination is invalid, a ``PyiCloudFailedLoginException`` exception is thrown.
|
|
|
|
You can also store your password in the system keyring using the command-line tool:
|
|
|
|
>>> icloud --username=jappleseed@apple.com
|
|
ICloud Password for jappleseed@apple.com:
|
|
Save password in keyring? (y/N)
|
|
|
|
If you have stored a password in the keyring, you will not be required to provide a password when interacting with the command-line tool or instantiating the ``PyiCloudService`` class for the username you stored the password for.
|
|
|
|
>>> api = PyiCloudService('jappleseed@apple.com')
|
|
|
|
If you would like to delete a password stored in your system keyring, you can clear a stored password using the ``--delete-from-keyring`` command-line option:
|
|
|
|
>>> icloud --username=jappleseed@apple.com --delete-from-keyring
|
|
|
|
=======
|
|
Devices
|
|
=======
|
|
|
|
You can list which devices associated with your account by using the ``devices`` property:
|
|
|
|
>>> 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:
|
|
|
|
>>> 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:
|
|
|
|
>>> 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
|
|
==============
|
|
|
|
Once you have successfully authenticated, you can start querying your data!
|
|
|
|
********
|
|
Location
|
|
********
|
|
|
|
Returns the device's last known location. The Find My iPhone app must have been installed and initialized.
|
|
|
|
>>> api.iphone.location()
|
|
{u'timeStamp': 1357753796553, u'locationFinished': True, u'longitude': -0.14189, u'positionType': u'GPS', u'locationType': None, u'latitude': 51.501364, u'isOld': False, u'horizontalAccuracy': 5.0}
|
|
|
|
******
|
|
Status
|
|
******
|
|
|
|
The Find My iPhone response is quite bloated, so for simplicity's sake this method will return a subset of the properties.
|
|
|
|
>>> api.iphone.status()
|
|
{'deviceDisplayName': u'iPhone 5', 'deviceStatus': u'200', 'batteryLevel': 0.6166913, 'name': u"Peter's iPhone"}
|
|
|
|
If you wish to request further properties, you may do so by passing in a list of property names.
|
|
|
|
**********
|
|
Play Sound
|
|
**********
|
|
|
|
Sends a request to the device to play a sound, if you wish pass a custom message you can do so by changing the subject arg.
|
|
|
|
>>> api.iphone.play_sound()
|
|
|
|
A few moments later, the device will play a ringtone, display the default notification ("Find My iPhone Alert") and a confirmation email will be sent to you.
|
|
|
|
*********
|
|
Lost Mode
|
|
*********
|
|
|
|
Lost mode is slightly different to the "Play Sound" functionality in that it allows the person who picks up the phone to call a specific phone number *without having to enter the passcode*. Just like "Play Sound" you may pass a custom message which the device will display, if it's not overridden the custom message of "This iPhone has been lost. Please call me." is used.
|
|
|
|
>>> phone_number = '555-373-383'
|
|
>>> message = 'Thief! Return my phone immediately.'
|
|
>>> api.iphone.lost_device(phone_number, message)
|
|
|
|
========
|
|
Calendar
|
|
========
|
|
|
|
The calendar webservice currently only supports fetching events.
|
|
|
|
******
|
|
Events
|
|
******
|
|
|
|
Returns this month's events:
|
|
|
|
>>> api.calendar.events()
|
|
|
|
Or, between a specific date range:
|
|
|
|
>>> from_dt = datetime(2012, 1, 1)
|
|
>>> to_dt = datetime(2012, 1, 31)
|
|
>>> api.calendar.events(from_dt, to_dt)
|
|
|
|
Alternatively, you may fetch a single event's details, like so:
|
|
|
|
>>> api.calendar.get_event_detail('CALENDAR', 'EVENT_ID')
|
|
|
|
========
|
|
Contacts
|
|
========
|
|
|
|
You can access your iCloud contacts/address book through the ``contacts`` property:
|
|
|
|
>>> for c in api.contacts.all():
|
|
>>> print c.get('firstName'), c.get('phones')
|
|
John [{u'field': u'+1 555-55-5555-5', u'label': u'MOBILE'}]
|
|
|
|
Note: These contacts do not include contacts federated from e.g. Facebook, only the ones stored in iCloud.
|
|
|
|
=======================
|
|
File Storage (Ubiquity)
|
|
=======================
|
|
|
|
You can access documents stored in your iCloud account by using the ``files`` property's ``dir`` method:
|
|
|
|
>>> 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:
|
|
|
|
>>> 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``.
|
|
|
|
>>> 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:
|
|
|
|
>>> 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:
|
|
|
|
>>> 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())
|