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
Autodrive is designed to make it as easy as possible to interact with the Google Drive and Sheets APIs via Python

Autodrive Autodrive is designed to make it as easy as possible to interact with the Google Drive and Sheets APIs via Python. It is especially designed

Chris Larabee 1 Oct 02, 2021
WhatsApp Web API client with multi-device support

Tauros WhatsApp Web client for multi-device in python Free software: MIT Documentation: https://tauros.readthedocs.io Features TODO Credits This packa

Manjit Pardeshi 0 Jan 20, 2022
A Discord Bot for the Pygame Community Server

PygameCommunityBot The Pygame Community Discord bot The bot is capable of doing a lot of stuff, the command prefix is pg!. For help on all the bot com

PygameCommunityDiscord 23 Nov 30, 2022
OpenSea Python Bot coded purely in Python3.

OpenSea Python Bot coded purely in Python3. It utilises everything from OpenSea API to continuously monitor NFT's. It can be used to snipe or monitor if something falls below floor value.

OpenSea Elite Sniper 20 Dec 29, 2021
Find Roblox Groups & Send To Discord

Roblox-Group-Finder A tool to help you find ownerless roblox groups :) It's really easy, all you need is a discord webhook :) It will not send locked

1 Dec 13, 2021
Telegram Bot for updating ongoing matches of Fotmob.com in channel by @AbirHasan2005

Fotmob-Bot A very simple Telegram Bot which will update ongoing matches of Fotmob in a channel. Demo Channel Configs API_ID - Get this from @TeleORG_B

Abir Hasan 22 Oct 21, 2022
Python library for interacting with the Wunderlist 2 REST API

Overview Wunderpy2 is a thin Python library for accessing the official Wunderlist 2 API. What does a thin library mean here? Only the bare minimum of

mieubrisse 24 Dec 29, 2020
Telegram Link Shortener Bot (With 20 Shorteners)

Telegram ShortenerBot ShortenerBot: 🇬🇧 Telegram Link Shortener Bot (11 + 9 Shorteners) 🇹🇷 Telegram Link Kısaltıcı Bot (11 + 9 Kısaltıcı) All suppo

Hüzünlü Artemis [HuzunluArtemis] 10 May 24, 2022
A Happy and lightweight Python Package that Provides an API to search for articles on Google News and returns a JSON response.

A Happy and lightweight Python Package that Provides an API to search for articles on Google News and returns a JSON response.

Muhammad Abdullah 273 Dec 31, 2022
🤖 Automated follow/unfollow bot for GitHub. Uses GitHub API. Written in python.

GitHub Follow Bot Table of Contents Disclaimer How to Use Install requirements Authenticate Get a GitHub Personal Access Token Add your GitHub usernam

João Correia 37 Dec 27, 2022
⛑ REDCap API interface in Python

REDCap API in Python Description Supports structured data extraction for REDCap projects. The API module d3b_redcap_api.redcap.REDCapStudy can be logi

D3b 1 Nov 21, 2022
Raphtory-client - The python client for the Raphtory project

Raphtory Client This is the python client for the Raphtory project Install via p

Raphtory 5 Apr 28, 2022
Reads and prints information from the website MalAPI.io

MalAPIReader Reads and prints information from the website MalAPI.io optional arguments:

Squiblydoo 16 Nov 10, 2022
Growtopia server_data.php reader with bypass method, using discord bot

Server_data.php-reader Growtopia server_data.php reader with bypass method, using discord bot How to use 1 install python 2 change your bot token

7 Jul 16, 2022
A free tempmail api for your needs!

Tempmail A free tempmail api for your needs! Website · Report Bug · Request Feature Features Add your own private domains Easy to use documentation No

dropout 10 Oct 26, 2021
The successor of GeoSnipe, a pythonic Minecraft username sniper based on AsyncIO.

OneSnipe The successor of GeoSnipe, a pythonic Minecraft username sniper based on AsyncIO. Documentation View Documentation Features • Mojang & Micros

1 Jan 14, 2022
A tiktok autoclaimer/sniper used to get og/rare usernames on tiktok.com

TikTok Autoclaimer A tiktok autoclaimer/sniper used to get og/rare usernames on tiktok.com Report Bug · Request Feature Features Asynchronous User fri

dropout 24 Dec 08, 2022
Auto Liker, Auto Reaction, Auto Comment, Auto Follower Tool. RajeLiker Credit Hacker.

Auto Liker, Auto Reaction, Auto Comment, Auto Follower Tool. RajeLiker Credit Hacker. Unlimited RajeLiker Credit Hack. Thanks To RajeLiker.

Md. Mehedi Hasan 32 Dec 28, 2022
StringSessionGenerator - A Telegram bot to generate pyrogram and telethon string session

⭐️ String Session Generator ⭐️ Genrate String Session Using this bot. Made by TeamUltronX 🔥 String Session Demo Bot: Environment Variables Mandatory

TheUltronX 1 Dec 31, 2021
Balsam Python client API & SDK

balsam No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) This Python package is automatically

Darren Govoni 1 Oct 22, 2021