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
YouTube bot, this is just my introduction to api and requests, this isn't intended on being an actual view bot.

YouTube bot, this is just my introduction to api and requests, this isn't intended on being an actual view bot.

Aran 2 Jul 25, 2022
Telegram Bot For Screenshot Generation.

Screenshotit_bot Telegram Bot For Screenshot Generation. Description An attempt to implement the screenshot generation of telegram files without downl

1 Nov 06, 2021
A Powerfull Userbot Telegram PandaX_Userbot, Vc Music Userbot + Bot Manager based Telethon

Support ☑ CREDITS THANKS YOU VERRY MUCH FOR ALL Telethon Pyrogram TeamUltroid TeamUserge CatUserbot pytgcalls Dan Lainnya

22 Dec 25, 2022
Source code of u/pekofy_bot from reddit.

pekofy-bot Source code of u/pekofy_bot from reddit. Get more info about the bot here: https://www.reddit.com/user/pekofy_bot/comments/krxxol/pekofy_bo

32 Dec 25, 2022
摩尔庄园手游脚本

摩尔庄园 BlueStacks 脚本 手游上线,情怀再起,但面对游戏中枯燥无味的每日任务和资源采集,你是否觉得肝疼呢? 本项目通过生成 BlueStacks 模拟器的宏脚本,帮助玩家护肝。 使用脚本请阅读 使用方式 和对应的 功能及说明 联系 Telegram 频道 @mole61 Telegram

WH-2099 43 Dec 16, 2022
The worst but simplest webhook bot for GitHub and Matrix.

gh-bot gh-bot is maybe the worst (but simplest) Matrix webhook bot for Github. Example of commits: Example of workflow finished: Setting up Server You

Jae Lo Presti 4 Aug 18, 2022
Public API client for GETTR, a "non-bias [sic] social network," designed for data archival and analysis.

GoGettr GoGettr is an API client for GETTR, a "non-bias [sic] social network." (We will not reward their domain with a hyperlink.) GoGettr is built an

Stanford Internet Observatory 72 Dec 14, 2022
AutomaTik is an automation system for MikroTik devices with simplicity and security in mind.

AutomaTik Installation AutomaTik is an automation system for MikroTik devices with simplicity and security in mind. Winbox is the main tool for MikroT

Osman Kazdal 4 Dec 05, 2022
May or may not be work🚶

AnyDLBot There are multiple things I can do: 👉 All Supported Video Formats of https://rg3.github.io/youtube-dl/supportedsites.html 👉 Upload as file

Arun 2 Nov 16, 2021
聚合空间测绘搜索(Fofa,Zoomeye,Quake,Shodan,Censys,BinaryEdge)

#Search-Tools Search-Tools集合比较常见的网络空间探测引擎 Fofa,Zoomeye,Quake,Shodan,Censys,BinaryEdge 简单说明 ICO搜索目前只有Fofa,Shodan,Quake支持 代理设置是防止在API请求过于频繁,或者在实战中,好多红队打

311 Dec 16, 2022
Example of a discord bot in Python

discordbot.py Example of a discord bot in Python Requirements Python 3.8 or higher Discord Bot Setting Up Clone this repo or download the files Rename

Debert Jamie 1 Oct 23, 2021
L3DAS22 challenge supporting API

L3DAS22 challenge supporting API This repository supports the L3DAS22 IEEE ICASSP Grand Challenge and it is aimed at downloading the dataset, pre-proc

L3DAS 38 Dec 25, 2022
Paginator for Dis-Snek Python Discord API wrapper

snek-paginator Paginator for Dis-Snek Python Discord API wrapper Installation: pip install -U snek-paginator Basic Example: from dis_snek.client impo

1 Nov 04, 2021
toldium is a modular, fast, reliable and customizable multiplatform bot library for your communities

toldium The easy multiplatform bot toldium is a modular, fast, reliable and customizable multiplatform bot library for your communities, from a commun

Stockdroid Fans 5 Nov 03, 2021
TgMusicBot is a telegram userbot for playing songs in telegram voice calls based on Pyrogram and PyTgCalls.

TgMusicBot [Stable] TgMusicBot is a telegram userbot for playing songs in telegram voice calls based on Pyrogram and PyTgCalls. Commands !start / !hel

Kürşad 21 Dec 25, 2022
A python telegram bot to fetch the details of an ipadress with help of ip-api

ipfetcher A python(Pyrogram) oriented telegram bot to fetch the details of an ipadress developed by @riz4d with the API of https://ip-api.com Deployme

Mohamed Rizad 5 Mar 12, 2022
A fun hangman style game to guess random movie names with a short summary about the movie.

hang-movie-man Hangman but for movies 😉 This is a fun hangman style game to guess random movie names from the local database and show some summary ab

Ankit Josh 10 Sep 07, 2022
A powerfull SMS Bomber for Bangladesh . NO limite .Unlimited SMS Spaming

RedBomberBD A powerfull SMS Bomber for Bangladesh . NO limite .Unlimited SMS Spaming Installation Install my-tool on termux by using thoes commands pk

Abdullah Al Redwan 3 Feb 16, 2022
A basic Ubisoft API wrapper created in python.

UbisoftAPI A basic Ubisoft API wrapper created in python. I will be updating this with more endpoints as time goes on. Please note that this is my fir

Ethan 2 Oct 31, 2021
BSDotPy, A module to get a bombsquad player's account data.

BSDotPy BSDotPy, A module to get a bombsquad player's account data from bombsquad's servers. Badges Provided By: shields.io Acknowledgements Issues Pu

Rudransh Joshi 3 Feb 17, 2022