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
Python: Asynchronous client for the Tailscale API

Python: Asynchronous client for the Tailscale API Asynchronous client for the Tailscale API. About This package allows you to control and monitor Tail

Franck Nijhof 9 Nov 22, 2022
Nonebot2 简易群管

简易群管 ✨ NoneBot2 简易群管 ✨ _ 踢 改 禁 欢迎issue pr 权限说明:permission=SUPERUSER 安装 💿 pip install nonebot-plugin-admin 导入 📲 在bot.py 导入,语句: nonebot.load_plugin("n

幼稚园园长 74 Dec 22, 2022
A minimal open source mtg-like tcg game made in python that can be played on a terminal emulator using a keyboard.

TCG-TERM Project state: 🔧 🚧 🚧 🚧 Incomplete, In development 🚧 🚧 🚧 👷 (Keep in mind that at the moment, This project is currently undone, and wil

Amos 3 Aug 29, 2021
Editing a Tinder bot created by @frederikme

tinder_bot_edit Editing a Tinder bot created by @frederikme Table of Contents Basic Swipe Bot Basic Swipe Bot Download the code as a zip or clone the

Alex Carter 1 Nov 23, 2021
Many discord bots serving different purposes

Discord_Botlari Farklı amaçlara hizmet eden bir çok discord botu En kapsamlı Bot Game Bottur. bir oyun botudur discord sunucularında kullanılır. (tüm

1 Dec 21, 2021
AWS Serverless Application Model (SAM) is an open-source framework for building serverless applications

AWS Serverless Application Model (AWS SAM) The AWS Serverless Application Model (SAM) is an open-source framework for building serverless applications

Amazon Web Services 8.9k Dec 31, 2022
This is a small Messnger with the cmd as an interface

Messenger This is a small messenger with the cmd as an interface. It started as a project to learn more about Python 3. If you want to run a version o

1 Feb 24, 2022
ABACUS Aroio API for Webinterfaces and App-Connections

ABACUS Aroio API for Webinterfaces and App-Connections Setup Start virtual python environment if you don't have python3 running setup: $ python3 -m ve

Abacus Aroio Developer Team 1 Apr 01, 2021
A GitHub Actions repo for tracking the dummies sending free money to Alex Jones + co.

A GitHub Actions repo for tracking the dummies sending free money to Alex Jones + co.

Egarok 2 Jul 20, 2022
Projeto Informações Conta do Instagram - Instagram Account Information Project

VESTA-tools A collection of simple tools that proved to be needed for handling large periodic calculations with the VASP software package. distTotCalc

Thiago Souza 1 Dec 02, 2021
a simple quant trading bot with CLI interface

shepherd a simple quant trading bot with CLI interface CLI shell command docs coming soon after I brush up the code and add more features :) Minimal R

m00n 0 Jun 06, 2022
Building and deploying AWS Lambda Shared Layers

AWS Lambda Shared Layers This repository is hosting the code from the following blog post: AWS Lambda & Shared layers for Python. The goal of this rep

Flaneer 7 Dec 02, 2021
This repository contains unofficial code reproducing Agent57

Agent57 This repository contains unofficial code reproducing Agent57, which outp

19 Dec 29, 2022
Telegram Userbot to steram youtube live or Youtube vido in telegram vc by help of pytgcalls

TGVCVidioPlayerUB Telegram Userbot to steram youtube live or youtube vidio in telegram vc by help of pytgcalls Commands = Vidio Playing 🎧 stream :

Achu biju 3 Oct 28, 2022
Random-backlog-tweet - Pick a page from a sitemap at random and prep a tweet button for it

Random-backlog-tweet - Pick a page from a sitemap at random and prep a tweet button for it

Paul O'Leary McCann 0 Dec 01, 2022
Process your transactions from etherscan (and other forks) into excel file for easier manipulation.

DEGEN TRACKER Read first This is my first Python open source project and it is very likely full of bad practices and security issues. You should not u

1 Oct 13, 2022
Mass-unscrobble Last.fm scrobbles based on artist, track title, or time of day of the scrobble.

Unscrobbler This program is designed to mass-unscrobble Last.fm scrobbles based on artist, track title, or time of day of the scrobble. For example, i

Nathan 6 Nov 04, 2022
This is a simple bot for running Python code through Discord

Python Code Runner Discord Bot This is a simple bot for running Python code through Discord. It was originally developed for the Beginner.Codes Discor

beginner.py 1 Feb 14, 2022
GitHub Activity Generator - A script that helps you instantly generate a beautiful GitHub Contributions Graph for the last year.

GitHub Activity Generator A script that helps you instantly generate a beautiful GitHub Contributions Graph for the last year. Before 😐 😶 😒 After ?

1 Dec 30, 2021
A Python Client to View F1TV Content the right way

F1Hub is a terminal application running directly on your computer -- no connection to the website needed* *In theory. As of now, the F1TV website is needed for some content

kodos 3 Jun 14, 2022