iCloudPy is a simple iCloud webservices wrapper library written in Python

Overview

iCloudPy

CI - Main Tests Coverage Discord Buy Me A Coffee

🀟 Please star this repository if you end up using the library. It will help me continue supporting this product. πŸ™

iCloudPy is a simple iCloud webservices wrapper library written in Python. It is a major reuse of pyiCloud python library.

iCloudPy connects to iCloud using your username and password, stores the session locally and then performs various queries to iCloud server.

Authentication

Authentication without using a saved password is as simple as passing your username and password to the ICloudPyService class:

from icloudpy import ICloudPyService
api = ICloudPyService('[email protected]', 'password')

In the event that the username/password combination is invalid, a ICloudPyFailedLoginException exception is thrown.

You can also store your password in the system keyring using the command-line tool:

> icloud [email protected]
ICloud Password for [email protected]:
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 ICloudPyService class for the username you stored the password for.

api = ICloudPyService('[email protected]')

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 [email protected] --delete-from-keyring

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.

Two-step and two-factor authentication (2SA/2FA)

If you have enabled two-factor authentications (2FA) or two-step authentication (2SA) for the account you will have to do some extra work:

    if api.requires_2fa:
        print "Two-factor authentication required."
        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:
        import click
        print "Two-step authentication required. Your trusted devices are:"

        devices = api.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 api.send_verification_code(device):
            print "Failed to send verification code"
            sys.exit(1)

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

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': 
   
    '
   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==']

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

The object returned from the above open method is a response object and the open method can accept any parameters you might normally use in a request using 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())

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:

>>> 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
u'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
u'file'

The open method will return a response object from which you can read the file's contents:

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

To interact with files and directions the mkdir, rename and delete functions are available for a file or folder:

>>> api.drive['Holiday Photos'].mkdir('2020')
>>> api.drive['Holiday Photos']['2020'].rename('2020_copy')
>>> api.drive['Holiday Photos']['2020_copy'].delete()

The upload method can be used to send a file-like object to the iCloud Drive:

>>> with open('Vacation.jpeg', 'rb') as file_in:
>>>>    api.drive['Holiday Photos'].upload(file_in)

It is strongly suggested to open file handles as binary rather than text to prevent decoding errors further down the line.

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['Screenshots']
<PhotoAlbum: 'Screenshots'>

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) :

>>> for photo in api.photos.albums['Screenshots']:
        print photo, photo.filename
<PhotoAsset: id=AVbLPCGkp798nTb9KZozCXtO7jds> IMG_6045.JPG

To download a photo use the download method, which will return a response object, initialized with stream set to True, so you can read from the raw response object:

>>> photo = next(iter(api.photos.albums['Screenshots']), None)
>>> 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'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())
Comments
  • [BUG] icloudpy fails to login as a user

    [BUG] icloudpy fails to login as a user

    Describe the bug ICloudPyService fails to get login session

    To Reproduce Steps to reproduce the behavior:

    1. run api = ICloudPyService(username, password)
    2. command raises error ("missing apple_id field")

    Expected behavior Command completes successfully

    It looks like Apple has changed, perhaps for only some connections, the headers it returns iin response to a login attempt. Thus it fails to get dsWebAuthToken and then _authenticate_with_token fails.

    bug 
    opened by mchonofsky 1
  • [BUG] KeyError: 'data_token'

    [BUG] KeyError: 'data_token'

    Describe the bug Failed to download /app/icloud/drive/<path>/<to>/<file>: 'data_token' error for some of .pages documents in drive. To Reproduce Steps to reproduce the behavior: api = ICloudPyService(username.strip(), password.strip()) drive = api.drive for i in api.drive.dir() item = drive[i] if item.type == "file": localfile = os.path.join('.', item.name) with item.open(stream=True) as response: with open(localfile, "wb") as file_out: copyfileobj(response.raw, file_out)

    This produces a KeyError: 'data_token' before this patch when a packaged file like a Pages is attempted to be downloaded.

    Expected behavior File should be downloaded without error.

    Screenshots NA Configuration Default configuration.

    Additional context From: https://github.com/mandarons/icloud-drive-docker/issues/11

    bug 
    opened by mandarons 1
  • [BUG] Cannot import on python 3.10

    [BUG] Cannot import on python 3.10

    Describe the bug Import failed due to very old version of keyring specified in requirements.txt. What disrupts upgrading it?

    To Reproduce

    • from icloudpy import ICloudPyService

    Expected behavior Correctly imported.

    Logs

    ImportError                               Traceback (most recent call last)
    /workspaces/iCloud_drive_uploader/test.ipynb Cell 3' in <cell line: 2>()
          [1] import sys
    ----> [2] from icloudpy import ICloudPyService
          [3] api = ICloudPyService(os.getenv('icu'), os.getenv('icp'))
          [4] if api.requires_2fa:
    
    File ~/.local/lib/python3.10/site-packages/icloudpy/__init__.py:3, in <module>
          1 """The iCloudPy library."""
          2 import logging
    ----> 3 from icloudpy.base import ICloudPyService
          5 logging.getLogger(__name__).addHandler(logging.NullHandler())
    
    File ~/.local/lib/python3.10/site-packages/icloudpy/base.py:20, in <module>
         12 import getpass
         14 from icloudpy.exceptions import (
         15     ICloudPyFailedLoginException,
         16     ICloudPyAPIResponseException,
         17     ICloudPy2SARequiredException,
         18     ICloudPyServiceNotActivatedException,
         19 )
    ---> 20 from icloudpy.services import (
         21     FindMyiPhoneServiceManager,
         22     CalendarService,
         23     UbiquityService,
         24     ContactsService,
         25     RemindersService,
         26     PhotosService,
         27     AccountService,
         28     DriveService,
         29 )
         30 from icloudpy.utils import get_password_from_keyring
         33 LOGGER = logging.getLogger(__name__)
    
    File ~/.local/lib/python3.10/site-packages/icloudpy/services/__init__.py:8, in <module>
          6 from icloudpy.services.reminders import RemindersService
          7 from icloudpy.services.photos import PhotosService
    ----> 8 from icloudpy.services.account import AccountService
          9 from icloudpy.services.drive import DriveService
    
    File ~/.local/lib/python3.10/site-packages/icloudpy/services/account.py:6, in <module>
          3 from six import PY2, python_2_unicode_compatible
          4 from collections import OrderedDict
    ----> 6 from icloudpy.utils import underscore_to_camelcase
          9 class AccountService(object):
         10     """The 'Account' iCloud service."""
    
    File ~/.local/lib/python3.10/site-packages/icloudpy/utils.py:3, in <module>
          1 """Utils."""
          2 import getpass
    ----> 3 import keyring
          4 from sys import stdout
          6 from .exceptions import ICloudPyNoStoredPasswordAvailableException
    
    File ~/.local/lib/python3.10/site-packages/keyring/__init__.py:6, in <module>
          3 import logging
          4 logger = logging.getLogger('keyring')
    ----> 6 from .core import (set_keyring, get_keyring, set_password, get_password,
          7                   delete_password)
          8 from .getpassbackend import get_password as get_pass_get_password
         10 try:
    
    File ~/.local/lib/python3.10/site-packages/keyring/core.py:14, in <module>
         11 from .py33compat import max
         13 from . import logger
    ---> 14 from . import backend
         15 from .util import platform_ as platform
         16 from .util import once
    
    File ~/.local/lib/python3.10/site-packages/keyring/backend.py:18, in <module>
         16 from . import errors, util
         17 from . import backends
    ---> 18 from .util import properties
         19 from .py27compat import add_metaclass, filter
         22 log = logging.getLogger(__name__)
    
    File ~/.local/lib/python3.10/site-packages/keyring/util/properties.py:1, in <module>
    ----> 1 from collections import Callable
          3 class ClassProperty(property):
          4     """
          5     An implementation of a property callable on a class. Used to decorate a
          6     classmethod but to then treat it like a property.
       (...)
         19     False
         20     """
    
    ImportError: cannot import name 'Callable' from 'collections' (/usr/local/lib/python3.10/collections/__init__.py)
    
    bug 
    opened by c01o 0
  • [FEATURE] AsyncIO-based service class - ICloudPyAsync

    [FEATURE] AsyncIO-based service class - ICloudPyAsync

    Use case As an iCloud user who has several gigs of data, I want to download all of my data and keep it in sync locally faster so that I can be more productive.

    Describe the solution you'd like Currently, this library performs sequential downloading of iCloud data. This is a huge performance bottleneck especially for media and documents (e.g. https://github.com/mandarons/icloud-drive-docker). Downloading from iCloud servers is inherently IO-bound. Using AsyncIO should significantly boost download performance.

    Describe alternatives you've considered Alternative can be multithreading. However, it is not optimal as IO-bound operations will continue to throttle all threads.

    Additional context Some relevant info: https://medium.com/radix-ai-blog/performant-http-with-aiohttp-in-python-3-756580e54eff

    enhancement 
    opened by mandarons 0
  • [ENHANCEMENT] Fix Lint errors for improved code quality

    [ENHANCEMENT] Fix Lint errors for improved code quality

    Describe the bug Lint is failing - fix it for improved code quality.

    To Reproduce Steps to reproduce the behavior:

    1. Run run-ci.sh
    2. See lint errors

    Expected behavior No failure during lint step. Code quality is 10/10.

    Screenshots If applicable, add screenshots to help explain your problem.

    Configuration If applicable, please share the configuration details

    Additional context Add any other context about the problem here.

    enhancement 
    opened by mandarons 0
Releases(0.3.2)
Owner
Mandar Patil
Being lazy...
Mandar Patil
Andrei 1.4k Dec 24, 2022
Telegram bot with various Sticker Tools

Sticker Tools Bot @Sticker_Tools_Bot A star ⭐ from you means a lot to us! Telegram bot with various Sticker Tools Usage Deploy to Heroku Tap on above

Stark Bots 20 Dec 08, 2022
A Telegram Music Bot with proper functions written in Python with Pyrogram and Py-Tgcalls.

⭐️ Yukki Music Bot ⭐️ A Telegram Music Bot written in Python using Pyrogram and Py-Tgcalls Ready to use method A Support Group and ready-to-use runnin

Shikhar Kumar 1000 Jan 03, 2023
Attempting to create a framework for Discord Slash commands... yes

discord_slash.py Attempting to create a framework for Discord Slash commands... yes Installation pip install slashpy Documentation Coming soonβ„’ Why is

AlexFlipnote 11 Mar 24, 2021
A Python Module That Uses ANN To Predict A Stocks Price And Also Provides Accurate Technical Analysis With Many High Potential Implementations!

Stox ⚑ A Python Module For The Stock Market ⚑ A Module to predict the "close price" for the next day and give "technical analysis". It uses a Neural N

Dopevog 31 Dec 16, 2022
Creating a Python API, for the MakeMyTrip Flight Schedules.

MakeMyTripAPI Creating a Python API, for the MakeMyTrip Flight Schedules. Source: MakeMyTrip is an Indian online travel company founded in 2000. Headq

Aman Priyanshu 0 Jan 12, 2022
A reddit bot that imitates the popular reddit bot "u/repostsleuthbot" to trick people into clicking on a rickroll

Reddit-Rickroll-Bot A reddit bot that imitates the popular reddit bot "u/repostsleuthbot" to trick people into clicking on a rickroll Made with The Py

0 Jul 16, 2022
Is the CoWin website updated for registration?

CoWin-Update Is the CoWin website updated for registration? This is a very hacky PYTHON3 script to lookup the CoWin portal if they re-deployed their J

Yash Jakhotiya 5 May 10, 2021
My Discord Bot that I used to learn Python. Please disregard the unstructured code!

Botsche My personal Discord Bot. To run this bot, change TOKEN in config.ini to your Discord Bot Token, which can be retrieved from your Discord Dev

Mats Voss 1 Nov 29, 2021
Get Notified about vaccine availability in your location on email & sms βœ‰οΈ! Vaccinator Octocat tracks & sends personalised vaccine info everday. Go get your shot ! πŸ’‰

Vaccinater Get Notified about vaccine availability in your location on email & sms βœ‰οΈ ! Vaccinator Octocat tracks & sends personalised vaccine info ev

Mayukh Pankaj 6 Apr 28, 2022
Signs API calls to SberCloud.Advanced with AK/SK

sbercloud-api-aksk Signs API calls to SberCloud.Advanced with AK/SK This script is a courtesy of @sadpdtchr Description Sometimes there is a need to m

Peter Predtechensky 1 Nov 30, 2021
Nyon-stream - A python script that uses webtorrent to stream nyaa videos directly to mpv

nyon-stream A rather shitty script that uses webtorrent to stream nyaa videos di

18 Feb 08, 2022
Unarchive Bot for Telegram

Telegram UnArchiver Bot UnArchiveBot: πŸ‡¬πŸ‡§ Bot that allows you to extract supported archive formats in telegram. πŸ‡ΉπŸ‡· Desteklenen arşiv biΓ§imleri tele

HΓΌzΓΌnlΓΌ Artemis [HuzunluArtemis] 25 May 07, 2022
Get an SNS alert for High Severity GuardDuty findings

Automation AWS-GuardDuty findings Get an SNS alert for High Severity GuardDuty findings Problem: Getting notified when there is Red finding in AWS Gua

Giten Mitra 4 Nov 18, 2022
Very Sempil Bot Auto Filter bot

SAMANTHA_BOT Very Sempil Bot Auto Filter bot ##[ # π‚π‹πˆπ‚πŠ ππ„π‹πŽπ– πˆπŒπ€π†π„ π“πŽ πƒπ„ππ‹πŽπ˜ πŸ‘‡ πŸ‘‡ πŸ‘‡ Auto Filter Manuel Filter IMDB Admin Co

DARK WEBLOAD 3 Jun 27, 2022
Repositorio dedicado a contener los archivos fuentes del bot de discord "Lector de Ejercicios".

Lector de Ejercicios Este bot de discord estΓ‘ pensado para usarse ΓΊnicamente en el discord de la materia Algoritmos y ProgramaciΓ³n I, de la Facultad d

Franco Lighterman Reismann 3 Sep 17, 2022
This Server Cloner can clone the server you want with all the perms of roles in every particular channel.

Server-Cloner-with-perms πŸš€ This Server Cloner can clone the server you want with all the perms of roles in every particular channel. Features Clone C

Gripz 0 Feb 17, 2022
Orca is an extensive and extendable Python 3.x library for the Discord API.

Orca is an extensive and extendable Python 3.x library for the Discord API.

RPS 4 Apr 03, 2022
Slam Mirror Bot is a multipurpose Telegram Bot written in Python for mirroring files on the Internet to our beloved Google Drive.

Slam Mirror Bot is a multipurpose Telegram Bot written in Python for mirroring files on the Internet to our beloved Google Drive.

Abinash939 1 Oct 10, 2021
PepeSniper is an open-source Discord Nitro auto claimer/redeemer made in python.

PepeSniper is an open-source Discord Nitro auto claimer made in python. It sure as hell is not the fastest sniper out there but it gets the job done in a timely and stable manner. It also supports ho

Unknown User 1 Dec 22, 2021