No description
Find a file
Tor Arne Vestbø 8e45bf98db Don't set host header in reminders service
It's automatically taken care of by the requests module based on
the URL, and explicitly setting it on the globally shared session
object results in other services (such as the calendar service)
breaking when used after the reminders service in the same session.
2016-03-12 14:48:23 +01:00
pyicloud Don't set host header in reminders service 2016-03-12 14:48:23 +01:00
.gitignore Adding tox configuration; making sanity test Python 2.6 compatible. 2014-10-05 20:59:30 -07:00
.travis.yml Drops support for Python 2.6. 2016-01-29 18:26:02 -08:00
MANIFEST.in Removing original markdown version of the readme. 2014-08-26 23:49:10 -07:00
README.rst Add iCloud Photo Library service 2016-02-25 13:33:43 +01:00
requirements.txt Add iCloud Photo Library service 2016-02-25 13:33:43 +01:00
setup.cfg Adding tox configuration; making sanity test Python 2.6 compatible. 2014-10-05 20:59:30 -07:00
setup.py Release 0.8.0 2016-03-01 01:32:32 +01:00
tox.ini Release 0.6.0. 2014-10-25 20:37:22 -07:00

.. 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

*******************************
Two-factor authentication (2FA)
*******************************

If you have enabled two-factor authentication for the account you will have to do some extra work:

.. code-block:: python

	if icloud.requires_2fa:
	    print "Two-factor authentication required. Your trusted devices are:"

	    devices = icloud.trusted_devices
	    for i, device in enumerate(devices):
	        print "  %s: %s" % (i, device.get('deviceName',
	            "SMS to %s" % device.get('phoneNumber')))

	    device = click.prompt('Which device would you like to use?', default=0)
	    device = devices[device]
	    if not icloud.send_verification_code(device):
	        print "Failed to send verification code"
	        sys.exit(1)

	    code = click.prompt('Please enter validation code')
	    if not icloud.validate_verification_code(device, code):
	        print "Failed to verify verification code"
	        sys.exit(1)

Note: Both regular login and two-factor authentication will expire after an interval set by Apple, at which point you will have to re-authenticate. This interval is currently two months.
>>>>>>> Add support for two-factor authentication

=======
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())

=======================
Photo Library
=======================

You can access the iCloud Photo Library through the ``photos`` property.

>>> api.photos.all
<PhotoAlbum: 'All Photos'>

Individual albums are available through the ``albums`` property:

>>> api.photos.albums['Selfies']
<PhotoAlbum: 'Selfies'>

Which you can index or iterate to access the photo assets:

>>> for photo in api.photos.albums['Selfies']:
        print photo, photo.filename
<PhotoAsset: client_id=4429> IMG_6045.JPG

Metadata about photos is fetched on demand as you access properties of the ``PhotoAsset`` object, and are also prefetched to improve performance.

To download a photo use the `download` method, which will return a `response object <http://www.python-requests.org/en/latest/api/#classes>`_, initialized with ``stream`` set to ``True``, so you can read from the raw response object:

>>> photo = api.photos.albums['Selfies'][0]
>>> download = photo.download()
>>> with open(photo.filename, 'wb') as opened_file:
        opened_file.write(download.raw.read())

Note: Consider using ``shutil.copyfile`` or another buffered strategy for downloading the file so that the whole file isn't read into memory before writing.

Information about each version can be accessed through the ``versions`` property:

>>> photo.versions.keys()
[u'large', u'medium', u'original', u'thumb']

To download a specific version of the photo asset, pass the version to ``download()``:

>>> download = photo.download('thumb')
>>> with open(photo.versions['thumb'].filename, 'wb') as thumb_file:
        thumb_file.write(download.raw.read())