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
A file-based quote bot written in Python

Let's Write a Python Quote Bot! This repository will get you started with building a quote bot in Python. It's meant to be used along with the Learnin

A . S . M . RADWAN 2 Apr 03, 2022
42-event-notifier - 42 Event notifier using 42API and Github Actions

42 Event Notifier 42서울 Agenda에 새로운 이벤트가 등록되면 알려드립니다! 현재는 Github Issue로 등록되므로 상단

6 May 16, 2022
Monetize your apps with KivAds using Google AdMob api.

KivAds(WIP) Monetize your apps with KivAds using Google AdMob api. KivAds uses the latest version of Google AdMob sdk(version 20.0.0). KivAds exposes

Guhan Sensam 16 Nov 05, 2022
Simple stock price analytics

mune · Mune is an open source python web application built to analyze stocks, named after Homma Munehisa. Currently, the forecasting component is powe

Richard Hong 14 Aug 30, 2021
Local community telegram bot

Бот на районе Телеграм-бот для поиска адресов и заведений в вашем районе города или в небольшом городке. Требует недели прогулок по району д

Ilya Zverev 32 Jan 19, 2022
This is a simple Telegram bot to Delete User Messages based on Groupmembers Votes. Heroku deployable

ibCleaner Bot This is a simple Telegram bot to Delete User Messages based on Groupmembers Votes. Deploy to Heroku Deploy locally Edit config.py and ad

8 Oct 21, 2022
Discord.py-Bot-Template - Discord Bot Template with Python 3.x

Discord Bot Template with Python 3.x This is a template for creating a custom Di

Keagan Landfried 3 Jul 17, 2022
Telegram to TamTam stickers

Telegram to TamTam stickers @tg_stickers TamTam бот, который конвертирует Telegram стикеры в формат TamTam и помогает загрузить их в TamTam. Все делае

Ivan Buymov 22 Nov 01, 2022
An API wrapper around the pythonanywhere's API.

pyaww An API wrapper around the pythonanywhere's API. The name stands for pythonanywherewrapper. 100% api coverage most of the codebase is documented

7 Dec 11, 2022
Telegram tools

Telegram-Tools Telegram tools. Explanation English | 中文 Features Export group memebrs Add users to the group Send message to users Setup API Open http

4 Apr 02, 2022
A Python app which retrieves the rank and players' equipped skins during a match

VALORANT rank yoinker About The Project Usage Contributing Contact Acknowledgements Disclaimer About The Project Their Queue Current Skin Current Rank

Isaac Kenyon 270 Jan 04, 2023
MemeBot - A discord bot that tracks how good people's memes are

MemeBot A discord Meme "Karma" Tracking bot Dependancies Make sure you have pymongo installed and a mongodb cluster setup with two collections. pip in

Uday Sharma 3 Aug 10, 2022
JAKYM, Just Another Konsole YouTube-Music. A command line based Youtube music player written in Python with spotify and youtube playlist support

Just Another Konsole YouTube-Music Overview I wanted to create this application so that I could use the command line to play music easily. I often pla

Mayank Jha 73 Jan 01, 2023
A discord self bot that replies to messages using cleverbot

cleverbot-discord-self A discord self bot that replies to messages using cleverbot Bot will respond to DMs and channels in the channels list. Need to

0 Jan 11, 2022
The smart farm is an idea that designing Smart Farm by IoT

The smart farm is an idea that designing Smart Farm by IoT. Using Raspberry Pi 4 detect the data from different sensors(Raindrop sensor and DHT22 sensor), and push the data to Azure IoT central.

Jiage 1 Jan 11, 2022
Wrapper for shh/rsync for use with OpenFOAM and blue bear

bbsync wrapper for shh/rsync for use with OpenFOAM and blue bear About The Project bbsync is a wrapper for shh/rsync for use with OpenFOAM and blue be

1 Dec 10, 2021
Implement backup and recovery with AWS Backup across your AWS Organizations using a CI/CD pipeline (AWS CodePipeline).

Backup and Recovery with AWS Backup This repository provides you with a management and deployment solution for implementing Backup and Recovery with A

AWS Samples 8 Nov 22, 2022
Tools ini hanya bisa digunakan untuk menyerang website atau http/s

☢️ Tawkun DoS ☢️ Tools ini hanya bisa digunakan untuk menyerang website atau http/s FITUR: [ ☯️ ] Proxy Mode [ 🔥 ] SOCKS Mode | Kadang Eror [ ☢️ ] Ht

Bandhitawkunthi 9 Jul 19, 2022
Heroku app to explore boardgame data

A Dashboard for the Board Game Geeks among us Link to Application As many Board Game Geeks like myself track the scores of board game matches I decide

Maarten Grootendorst 20 Nov 23, 2022
MusicBot is the original Discord music bot written for Python 3.5+, using the discord.py library

The original MusicBot for Discord (formerly SexualRhinoceros/MusicBot)

Just Some Bots 2.9k Jan 02, 2023