Package: icloudpy
iCloudPy:loveyou_gesture: **_Please star this repository if you end up using this project. If it has improved your life in any way, consider donating for my effort using 'Buy Me a Coffee' button above. It will help me continue supporting this product.** :pray:iCloudPy is a simple iCloud webservices wrapper library written in Python. It is a major reuse ofpyiCloudpython library.iCloudPy connects to iCloud using yourusernameandpassword, stores the session locally and then performs various queries to iCloud server.AuthenticationAuthentication without using a saved password is as simple as passing your username and password to theICloudPyServiceclass:fromicloudpyimportICloudPyServiceapi=ICloudPyService('jappleseed@apple.com','password')# For China regionapi=ICloudPyService('jappleseed@apple.com','password'auth_endpoint="https://www.icloud.com.cn",setup_endpoint="https://setup.icloud.com.cn/setup/ws/1",)In the event that the username/password combination is invalid, aICloudPyFailedLoginExceptionexception is thrown.You can also store your password in the system keyring using the command-line tool:>icloud--username=jappleseed@apple.com
ICloudPasswordforjappleseed@apple.com:
Savepasswordinkeyring?(y/N)# For China region>icloud--username=jappleseed@apple.com--region=china
ICloudPasswordforjappleseed@apple.com:
Savepasswordinkeyring?(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 theICloudPyServiceclass for the username you stored the password for.api=ICloudPyService('jappleseed@apple.com')If you would like to delete a password stored in your system keyring, you can clear a stored password using the--delete-from-keyringcommand-line option:>icloud--username=jappleseed@apple.com--delete-from-keyringNote: 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 enabledtwo-factor authentications (2FA) or two-step authentication (2SA)for the account you will have to do some extra work:ifapi.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)ifnotresult:print("Failed to verify security code")sys.exit(1)ifnotapi.is_trusted_session:print("Session is not trusted. Requesting trust...")result=api.trust_session()print("Session trust result%s"%result)ifnotresult:print("Failed to request trust. You will likely be prompted for the code again in the coming weeks")elifapi.requires_2sa:importclickprint"Two-step authentication required. Your trusted devices are:"devices=api.trusted_devicesfori,deviceinenumerate(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]ifnotapi.send_verification_code(device):print"Failed to send verification code"sys.exit(1)code=click.prompt('Please enter validation code')ifnotapi.validate_verification_code(device,code):print"Failed to verify verification code"sys.exit(1)DevicesYou can list which devices associated with your account by using thedevicesproperty:>>>api.devices{u'i9vbKRGIcLYqJnXMd1b257kUWnoyEBcEh6yM+IfmiMLh7BmOpALS+w==':<AppleDevice(iPhone4S:JohnnyAppleseed's iPhone)>,u'reGYDh9XwqNWTGIhNBuEwP1ds0F/Lg5t/fxNbI4V939hhXawByErk+HYVNSUzmWV': <AppleDevice(MacBook Air 11": Johnny Appleseed'sMacBookAir)>}and you can access individual devices by either their index, or their ID:>>>api.devices[0]<AppleDevice(iPhone4S:JohnnyAppleseed's iPhone)>>>> api.devices['i9vbKRGIcLYqJnXMd1b257kUWnoyEBcEh6yM+IfmiMLh7BmOpALS+w==']<AppleDevice(iPhone 4S: Johnny Appleseed'siPhone)>or, as a shorthand if you have only one associated apple device, you can simply use theiphoneproperty to access the first device associated with your account:>>>api.iphone
<AppleDevice(iPhone4S:JohnnyAppleseed'siPhone)>Note: the first device associated with your account may not necessarily be your iPhone.Find My iPhoneOnce you have successfully authenticated, you can start querying your data!LocationReturns 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}StatusThe 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 SoundSends 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 ModeLost 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 numberwithout 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)CalendarThe calendar webservice currently only supports fetching events.EventsReturns 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')ContactsYou can access your iCloud contacts/address book through thecontactsproperty:>>>forcinapi.contacts.all():
>>>printc.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)NOTE: It seems like having app documents are no longer stored in Ubiquityand migrated to iCloud Drive. See below.You can access documents stored in your iCloud account by using thefilesproperty'sdirmethod:>>>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'].size1308134>>>api.files['com~apple~Notes']['Documents']['Some Document'].type
u'file'And when you have a file that you'd like to download, theopenmethod will return a response object from which you can read thecontent.>>>api.files['com~apple~Notes']['Documents']['Some Document'].open().content'Hello, these are the file contents'The object returned from the aboveopenmethod is aresponse objectand theopenmethod can accept any parameters you might normally use in a request usingrequests.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 thestreamkeyword argument, and read directly from the raw response object:>>>download=api.files['com~apple~Notes']['Documents']['big_file.zip'].open(stream=True)>>>withopen('downloaded_file.zip','wb')asopened_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 atapi.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.size2021698>>>drive_file.type
u'file'Theopenmethod will return a response object from which you can read the file's contents:>>>fromshutilimportcopyfileobj
>>>withdrive_file.open(stream=True)asresponse:
>>>withopen(drive_file.name,'wb')asfile_out:
>>>copyfileobj(response.raw,file_out)To interact with files and directions themkdir,renameanddeletefunctions 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()Theuploadmethod can be used to send a file-like object to the iCloud Drive:>>>withopen('Vacation.jpeg','rb')asfile_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.Accessing App DataTheget_app_nodemethod can be used to retrieve a node with app data (that is not shown inapi.drive.dir()). This is where the individual apps store related documents.>>>node=api.drive.get_app_node("XXXXXXXXXX.com.apple.iMovie")Ids of individual app data can be found in~/Library/Mobile Documents(can only be accessed in Terminal).~must be replaced with..Node can then be used just like any other node, supportingmkdir,rename,deleteand so on:>>> node.mkdir('2020')
>>> node.rename('2020_copy')
>>> node.delete()Photo LibraryYou can access the iCloud Photo Library through thephotosproperty.>>>api.photos.all
<PhotoAlbum:'All Photos'>Individual albums are available through thealbumsproperty:>>>api.photos.albums['Screenshots']<PhotoAlbum:'Screenshots'>Which you can iterate to access the photo assets. The 'All Photos' album is sorted byadded_dateso the most recently added photos are returned first. All other albums are sorted byasset_date(which represents the exif date) :>>>forphotoinapi.photos.albums['Screenshots']:printphoto,photo.filename
<PhotoAsset:id=AVbLPCGkp798nTb9KZozCXtO7jds>IMG_6045.JPGTo download a photo use thedownloadmethod, which will return aresponse object, initialized withstreamset toTrue, so you can read from the raw response object:>>>photo=next(iter(api.photos.albums['Screenshots']),None)>>>download=photo.download()>>>withopen(photo.filename,'wb')asopened_file:opened_file.write(download.raw.read())Note: Consider usingshutil.copyfileor 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 theversionsproperty:>>>photo.versions.keys()[u'medium',u'original',u'thumb']To download a specific version of the photo asset, pass the version todownload():>>>download=photo.download('thumb')>>>withopen(photo.versions['thumb']['filename'],'wb')asthumb_file:thumb_file.write(download.raw.read())