2020-03-20 14:06:22 +01:00
***** ***
pyiCloud
***** ***
2015-05-14 06:40:56 +02:00
.. image :: https://travis-ci.org/picklepete/pyicloud.svg?branch=master
2020-03-24 15:21:30 +01:00
:alt: Check out our test status at https://travis-ci.org/picklepete/pyicloud
:target: https://travis-ci.org/picklepete/pyicloud
2015-05-14 06:40:56 +02:00
2020-03-24 15:21:30 +01:00
.. image :: https://img.shields.io/pypi/v/pyicloud.svg
:alt: Library version
:target: https://pypi.org/project/pyicloud
2020-03-20 14:06:22 +01:00
2020-03-24 15:21:30 +01:00
.. image :: https://img.shields.io/pypi/pyversions/pyicloud.svg
:alt: Supported versions
:target: https://pypi.org/project/pyicloud
.. image :: https://pepy.tech/badge/pyicloud
:alt: Downloads
:target: https://pypi.org/project/pyicloud
2020-03-20 14:06:22 +01:00
.. image :: https://requires.io/github/Quentame/pyicloud/requirements.svg?branch=master
2020-03-24 15:21:30 +01:00
:alt: Requirements Status
2020-03-20 14:06:22 +01:00
:target: https://requires.io/github/Quentame/pyicloud/requirements/?branch=master
2020-03-24 14:54:43 +01:00
.. image :: https://img.shields.io/badge/code%20style-black-000000.svg
2020-03-24 15:21:30 +01:00
:alt: Formated with Black
2020-03-24 14:54:43 +01:00
:target: https://github.com/psf/black
2015-05-14 06:40:56 +02:00
2020-03-24 14:54:43 +01:00
.. image :: https://badges.gitter.im/Join%20Chat.svg
2020-03-24 15:21:30 +01:00
: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
2015-05-14 06:40:56 +02:00
2014-08-27 08:49:44 +02:00
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.
2014-08-26 22:48:40 +02:00
At its core, PyiCloud connects to iCloud using your username and password, then performs calendar and iPhone queries against their API.
2020-03-20 14:06:22 +01:00
2014-08-26 22:48:40 +02:00
Authentication
==============
2016-02-24 02:44:03 +01:00
Authentication without using a saved password is as simple as passing your username and password to the `` PyiCloudService `` class:
2014-08-26 22:48:40 +02:00
2022-02-16 20:19:10 +01:00
.. code-block :: python
from pyicloud import PyiCloudService
api = PyiCloudService('jappleseed@apple.com', 'password')
2014-08-26 22:48:40 +02:00
2016-01-13 21:05:21 +01:00
In the event that the username/password combination is invalid, a `` PyiCloudFailedLoginException `` exception is thrown.
2014-08-26 22:48:40 +02:00
2016-02-24 02:44:03 +01:00
You can also store your password in the system keyring using the command-line tool:
2022-02-16 20:19:10 +01:00
.. code-block :: console
$ icloud --username=jappleseed@apple.com
ICloud Password for jappleseed@apple.com:
Save password in keyring? (y/N)
2016-02-24 02:44:03 +01:00
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.
2022-02-16 20:19:10 +01:00
.. code-block :: python
api = PyiCloudService('jappleseed@apple.com')
2016-02-24 02:44:03 +01:00
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:
2022-02-16 20:19:10 +01:00
.. code-block :: console
$ icloud --username=jappleseed@apple.com --delete-from-keyring
2016-02-24 02:44:03 +01:00
2017-01-09 22:12:00 +01:00
**Note** : Authentication will expire after an interval set by Apple, at which point you will have to re-authenticate. This interval is currently two months.
2016-02-24 00:18:56 +01:00
2017-01-09 22:12:00 +01:00
Two-step and two-factor authentication (2SA/2FA)
***** ***** ***** ***** ***** ***** ***** ***** ***** ***
2021-02-01 13:22:29 +01:00
If you have enabled two-factor authentications (2FA) or `two-step authentication (2SA) <https://support.apple.com/en-us/HT204152> `_ for the account you will have to do some extra work:
2016-02-24 00:18:56 +01:00
.. code-block :: python
2021-02-01 13:22:29 +01:00
if api.requires_2fa:
2022-02-16 20:19:10 +01:00
print("Two-factor authentication required.")
2021-02-01 13:22:29 +01:00
code = input("Enter the code you received of one of your approved devices: ")
result = api.validate_2fa_code(code)
print("Code validation result: %s" % result)
if not result:
print("Failed to verify security code")
sys.exit(1)
if not api.is_trusted_session:
print("Session is not trusted. Requesting trust...")
result = api.trust_session()
print("Session trust result %s" % result)
if not result:
print("Failed to request trust. You will likely be prompted for the code again in the coming weeks")
elif api.requires_2sa:
2020-03-24 15:21:30 +01:00
import click
2022-02-16 20:19:10 +01:00
print("Two-step authentication required. Your trusted devices are:")
2020-03-24 15:21:30 +01:00
devices = api.trusted_devices
for i, device in enumerate(devices):
2022-02-16 20:19:10 +01:00
print(
" %s: %s" % (i, device.get('deviceName',
2020-03-24 15:21:30 +01:00
"SMS to %s" % device.get('phoneNumber')))
2022-02-16 20:19:10 +01:00
)
2020-03-24 15:21:30 +01:00
device = click.prompt('Which device would you like to use?', default=0)
device = devices[device]
if not api.send_verification_code(device):
2022-02-16 20:19:10 +01:00
print("Failed to send verification code")
2020-03-24 15:21:30 +01:00
sys.exit(1)
code = click.prompt('Please enter validation code')
if not api.validate_verification_code(device, code):
2022-02-16 20:19:10 +01:00
print("Failed to verify verification code")
2020-03-24 15:21:30 +01:00
sys.exit(1)
2016-02-24 00:18:56 +01:00
2014-08-26 22:48:40 +02:00
Devices
=======
2016-01-13 21:05:21 +01:00
You can list which devices associated with your account by using the `` devices `` property:
2014-08-26 22:48:40 +02:00
2022-02-16 20:19:10 +01:00
.. code-block :: pycon
>>> api.devices
{
'i9vbKRGIcLYqJnXMd1b257kUWnoyEBcEh6yM+IfmiMLh7BmOpALS+w==': <AppleDevice(iPhone 4S: Johnny Appleseed's iPhone)>,
'reGYDh9XwqNWTGIhNBuEwP1ds0F/Lg5t/fxNbI4V939hhXawByErk+HYVNSUzmWV': <AppleDevice(MacBook Air 11": Johnny Appleseed's MacBook Air)>
}
2014-08-26 22:48:40 +02:00
and you can access individual devices by either their index, or their ID:
2022-02-16 20:19:10 +01:00
.. code-block :: pycon
>>> api.devices[0]
<AppleDevice(iPhone 4S: Johnny Appleseed's iPhone)>
>>> api.devices['i9vbKRGIcLYqJnXMd1b257kUWnoyEBcEh6yM+IfmiMLh7BmOpALS+w==']
<AppleDevice(iPhone 4S: Johnny Appleseed's iPhone)>
2014-08-26 22:48:40 +02:00
2016-01-13 21:05:21 +01:00
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:
2014-08-26 22:48:40 +02:00
2022-02-16 20:19:10 +01:00
.. code-block :: pycon
>>> api.iphone
<AppleDevice(iPhone 4S: Johnny Appleseed's iPhone)>
2014-08-26 22:48:40 +02:00
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.
2022-02-16 20:19:10 +01:00
.. code-block :: pycon
>>> api.iphone.location()
{'timeStamp': 1357753796553, 'locationFinished': True, 'longitude': -0.14189, 'positionType': 'GPS', 'locationType': None, 'latitude': 51.501364, 'isOld': False, 'horizontalAccuracy': 5.0}
2014-08-26 22:48:40 +02:00
Status
***** *
The Find My iPhone response is quite bloated, so for simplicity's sake this method will return a subset of the properties.
2022-02-16 20:19:10 +01:00
.. code-block :: pycon
>>> api.iphone.status()
{'deviceDisplayName': 'iPhone 5', 'deviceStatus': '200', 'batteryLevel': 0.6166913, 'name': "Peter's iPhone"}
2014-08-26 22:48:40 +02:00
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.
2022-02-16 20:19:10 +01:00
.. code-block :: python
api.iphone.play_sound()
2014-08-26 22:48:40 +02:00
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.
2022-02-16 20:19:10 +01:00
.. code-block :: python
phone_number = '555-373-383'
message = 'Thief! Return my phone immediately.'
api.iphone.lost_device(phone_number, message)
2014-08-26 22:48:40 +02:00
2020-03-20 14:06:22 +01:00
2014-08-26 22:48:40 +02:00
Calendar
========
The calendar webservice currently only supports fetching events.
Events
***** *
Returns this month's events:
2022-02-16 20:19:10 +01:00
.. code-block :: python
api.calendar.events()
2014-08-26 22:48:40 +02:00
Or, between a specific date range:
2022-02-16 20:19:10 +01:00
.. code-block :: python
from_dt = datetime(2012, 1, 1)
to_dt = datetime(2012, 1, 31)
api.calendar.events(from_dt, to_dt)
2014-08-26 22:48:40 +02:00
Alternatively, you may fetch a single event's details, like so:
2022-02-16 20:19:10 +01:00
.. code-block :: python
api.calendar.get_event_detail('CALENDAR', 'EVENT_ID')
2014-08-26 22:48:40 +02:00
2020-03-20 14:06:22 +01:00
2016-01-14 00:07:06 +01:00
Contacts
========
You can access your iCloud contacts/address book through the `` contacts `` property:
2022-02-16 20:19:10 +01:00
.. code-block :: pycon
>>> for c in api.contacts.all():
>>> print(c.get('firstName'), c.get('phones'))
John [{'field': '+1 555-55-5555-5', 'label': 'MOBILE'}]
2016-01-14 00:07:06 +01:00
Note: These contacts do not include contacts federated from e.g. Facebook, only the ones stored in iCloud.
2020-03-20 14:06:22 +01:00
2014-08-26 22:48:40 +02:00
File Storage (Ubiquity)
=======================
2016-01-13 21:05:21 +01:00
You can access documents stored in your iCloud account by using the `` files `` property's `` dir `` method:
2014-08-26 22:48:40 +02:00
2022-02-16 20:19:10 +01:00
.. code-block :: pycon
>>> api.files.dir()
['.do-not-delete',
'.localized',
'com~apple~Notes',
'com~apple~Preview',
'com~apple~mail',
'com~apple~shoebox',
'com~apple~system~spotlight'
]
2014-08-26 22:48:40 +02:00
You can access children and their children's children using the filename as an index:
2022-02-16 20:19:10 +01:00
.. code-block :: pycon
>>> api.files['com~apple~Notes']
<Folder: 'com~apple~Notes'>
>>> api.files['com~apple~Notes'].type
'folder'
>>> api.files['com~apple~Notes'].dir()
['Documents']
>>> api.files['com~apple~Notes']['Documents'].dir()
['Some Document']
>>> api.files['com~apple~Notes']['Documents']['Some Document'].name
'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
'file'
2014-08-26 22:48:40 +02:00
2016-01-13 21:05:21 +01:00
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 `` .
2014-08-26 22:48:40 +02:00
2022-02-16 20:19:10 +01:00
.. code-block :: pycon
>>> api.files['com~apple~Notes']['Documents']['Some Document'].open().content
'Hello, these are the file contents'
2014-08-26 22:48:40 +02:00
2016-01-13 21:05:21 +01:00
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> `_ .
2014-08-26 22:48:40 +02:00
For example, if you know that the file you're opening has JSON content:
2022-02-16 20:19:10 +01:00
.. code-block :: pycon
>>> 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'
2014-08-26 22:48:40 +02:00
2016-01-13 21:05:21 +01:00
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:
2014-08-26 22:48:40 +02:00
2022-02-16 20:19:10 +01:00
.. code-block :: pycon
>>> 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())
2016-01-13 14:12:13 +01:00
2020-05-03 04:54:11 +02:00
File Storage (iCloud Drive)
===========================
You can access your iCloud Drive using an API identical to the Ubiquity one described in the previous section, except that it is rooted at `` ` api.drive ` `` :
2022-02-16 20:19:10 +01:00
.. code-block :: pycon
>>> api.drive.dir()
['Holiday Photos', 'Work Files']
>>> api.drive['Holiday Photos']['2013']['Sicily'].dir()
['DSC08116.JPG', 'DSC08117.JPG']
>>> drive_file = api.drive['Holiday Photos']['2013']['Sicily']['DSC08116.JPG']
>>> drive_file.name
'DSC08116.JPG'
>>> drive_file.date_modified
datetime.datetime(2013, 3, 21, 12, 28, 12) # NB this is UTC
>>> drive_file.size
2021698
>>> drive_file.type
'file'
2020-05-03 04:54:11 +02:00
The `` open `` method will return a response object from which you can read the file's contents:
2022-02-16 20:19:10 +01:00
.. code-block :: python
from shutil import copyfileobj
with drive_file.open(stream=True) as response:
with open(drive_file.name, 'wb') as file_out:
copyfileobj(response.raw, file_out)
2020-05-03 04:54:11 +02:00
2020-08-10 19:09:48 +02:00
To interact with files and directions the `` mkdir `` , `` rename `` and `` delete `` functions are available
for a file or folder:
2022-02-16 20:19:10 +01:00
.. code-block :: python
api.drive['Holiday Photos'].mkdir('2020')
api.drive['Holiday Photos']['2020'].rename('2020_copy')
api.drive['Holiday Photos']['2020_copy'].delete()
2020-08-10 19:09:48 +02:00
The `` upload `` method can be used to send a file-like object to the iCloud Drive:
2022-02-16 20:19:10 +01:00
.. code-block :: python
with open('Vacation.jpeg', 'rb') as file_in:
api.drive['Holiday Photos'].upload(file_in)
2020-08-10 19:09:48 +02:00
It is strongly suggested to open file handles as binary rather than text to prevent decoding errors
further down the line.
2020-03-20 14:06:22 +01:00
2016-01-13 14:12:13 +01:00
Photo Library
=======================
You can access the iCloud Photo Library through the `` photos `` property.
2022-02-16 20:19:10 +01:00
.. code-block :: pycon
>>> api.photos.all
<PhotoAlbum: 'All Photos'>
2016-01-13 14:12:13 +01:00
Individual albums are available through the `` albums `` property:
2022-02-16 20:19:10 +01:00
.. code-block :: pycon
>>> api.photos.albums['Screenshots']
<PhotoAlbum: 'Screenshots'>
2016-01-13 14:12:13 +01:00
2017-09-24 15:22:17 +02:00
Which you can iterate to access the photo assets. The 'All Photos' album is sorted by `added_date` so the most recently added photos are returned first. All other albums are sorted by `asset_date` (which represents the exif date) :
2016-01-13 14:12:13 +01:00
2022-02-16 20:19:10 +01:00
.. code-block :: pycon
>>> for photo in api.photos.albums['Screenshots']:
print(photo, photo.filename)
<PhotoAsset: id=AVbLPCGkp798nTb9KZozCXtO7jds> IMG_6045.JPG
2016-01-13 14:12:13 +01:00
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:
2022-02-16 20:19:10 +01:00
.. code-block :: python
photo = next(iter(api.photos.albums['Screenshots']), None)
download = photo.download()
with open(photo.filename, 'wb') as opened_file:
2016-01-13 14:12:13 +01:00
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:
2022-02-16 20:19:10 +01:00
.. code-block :: pycon
>>> photo.versions.keys()
['medium', 'original', 'thumb']
2016-01-13 14:12:13 +01:00
To download a specific version of the photo asset, pass the version to `` download() `` :
2022-02-16 20:19:10 +01:00
.. code-block :: python
download = photo.download('thumb')
with open(photo.versions['thumb']['filename'], 'wb') as thumb_file:
2016-01-13 14:12:13 +01:00
thumb_file.write(download.raw.read())
2020-03-21 14:49:32 +01:00
Code samples
============
If you wanna see some code samples see the `code samples file </CODE_SAMPLES.md> `_ .