Polish readme (#373)

This commit is contained in:
Hugo 2022-02-16 20:19:10 +01:00 committed by GitHub
parent 8c7ba2afb4
commit f96b0d8c24
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -40,24 +40,32 @@ Authentication
Authentication without using a saved password is as simple as passing your username and password to the ``PyiCloudService`` class: Authentication without using a saved password is as simple as passing your username and password to the ``PyiCloudService`` class:
>>> from pyicloud import PyiCloudService .. code-block:: python
>>> api = PyiCloudService('jappleseed@apple.com', 'password')
from pyicloud import PyiCloudService
api = PyiCloudService('jappleseed@apple.com', 'password')
In the event that the username/password combination is invalid, a ``PyiCloudFailedLoginException`` exception is thrown. In the event that the username/password combination is invalid, a ``PyiCloudFailedLoginException`` exception is thrown.
You can also store your password in the system keyring using the command-line tool: You can also store your password in the system keyring using the command-line tool:
>>> icloud --username=jappleseed@apple.com .. code-block:: console
ICloud Password for jappleseed@apple.com:
Save password in keyring? (y/N) $ icloud --username=jappleseed@apple.com
ICloud Password for jappleseed@apple.com:
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 ``PyiCloudService`` class for the username you stored the password for. 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 ``PyiCloudService`` class for the username you stored the password for.
>>> api = PyiCloudService('jappleseed@apple.com') .. code-block:: python
api = PyiCloudService('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-keyring`` command-line option: 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 --username=jappleseed@apple.com --delete-from-keyring .. code-block:: console
$ icloud --username=jappleseed@apple.com --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. **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.
@ -69,7 +77,7 @@ If you have enabled two-factor authentications (2FA) or `two-step authentication
.. code-block:: python .. code-block:: python
if api.requires_2fa: if api.requires_2fa:
print "Two-factor authentication required." print("Two-factor authentication required.")
code = input("Enter the code you received of one of your approved devices: ") code = input("Enter the code you received of one of your approved devices: ")
result = api.validate_2fa_code(code) result = api.validate_2fa_code(code)
print("Code validation result: %s" % result) print("Code validation result: %s" % result)
@ -87,48 +95,54 @@ If you have enabled two-factor authentications (2FA) or `two-step authentication
print("Failed to request trust. You will likely be prompted for the code again in the coming weeks") print("Failed to request trust. You will likely be prompted for the code again in the coming weeks")
elif api.requires_2sa: elif api.requires_2sa:
import click import click
print "Two-step authentication required. Your trusted devices are:" print("Two-step authentication required. Your trusted devices are:")
devices = api.trusted_devices devices = api.trusted_devices
for i, device in enumerate(devices): for i, device in enumerate(devices):
print " %s: %s" % (i, device.get('deviceName', print(
" %s: %s" % (i, device.get('deviceName',
"SMS to %s" % device.get('phoneNumber'))) "SMS to %s" % device.get('phoneNumber')))
)
device = click.prompt('Which device would you like to use?', default=0) device = click.prompt('Which device would you like to use?', default=0)
device = devices[device] device = devices[device]
if not api.send_verification_code(device): if not api.send_verification_code(device):
print "Failed to send verification code" print("Failed to send verification code")
sys.exit(1) sys.exit(1)
code = click.prompt('Please enter validation code') code = click.prompt('Please enter validation code')
if not api.validate_verification_code(device, code): if not api.validate_verification_code(device, code):
print "Failed to verify verification code" print("Failed to verify verification code")
sys.exit(1) sys.exit(1)
Devices Devices
======= =======
You can list which devices associated with your account by using the ``devices`` property: You can list which devices associated with your account by using the ``devices`` property:
>>> api.devices .. code-block:: pycon
{
u'i9vbKRGIcLYqJnXMd1b257kUWnoyEBcEh6yM+IfmiMLh7BmOpALS+w==': <AppleDevice(iPhone 4S: Johnny Appleseed's iPhone)>, >>> api.devices
u'reGYDh9XwqNWTGIhNBuEwP1ds0F/Lg5t/fxNbI4V939hhXawByErk+HYVNSUzmWV': <AppleDevice(MacBook Air 11": Johnny Appleseed's MacBook Air)> {
} 'i9vbKRGIcLYqJnXMd1b257kUWnoyEBcEh6yM+IfmiMLh7BmOpALS+w==': <AppleDevice(iPhone 4S: Johnny Appleseed's iPhone)>,
'reGYDh9XwqNWTGIhNBuEwP1ds0F/Lg5t/fxNbI4V939hhXawByErk+HYVNSUzmWV': <AppleDevice(MacBook Air 11": Johnny Appleseed's MacBook Air)>
}
and you can access individual devices by either their index, or their ID: and you can access individual devices by either their index, or their ID:
>>> api.devices[0] .. code-block:: pycon
<AppleDevice(iPhone 4S: Johnny Appleseed's iPhone)>
>>> api.devices['i9vbKRGIcLYqJnXMd1b257kUWnoyEBcEh6yM+IfmiMLh7BmOpALS+w=='] >>> api.devices[0]
<AppleDevice(iPhone 4S: Johnny Appleseed's iPhone)> <AppleDevice(iPhone 4S: Johnny Appleseed's iPhone)>
>>> api.devices['i9vbKRGIcLYqJnXMd1b257kUWnoyEBcEh6yM+IfmiMLh7BmOpALS+w==']
<AppleDevice(iPhone 4S: Johnny Appleseed'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: 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 .. code-block:: pycon
<AppleDevice(iPhone 4S: Johnny Appleseed's iPhone)>
>>> api.iphone
<AppleDevice(iPhone 4S: Johnny Appleseed's iPhone)>
Note: the first device associated with your account may not necessarily be your iPhone. Note: the first device associated with your account may not necessarily be your iPhone.
@ -142,16 +156,20 @@ Location
Returns the device's last known location. The Find My iPhone app must have been installed and initialized. Returns the device's last known location. The Find My iPhone app must have been installed and initialized.
>>> api.iphone.location() .. code-block:: pycon
{'timeStamp': 1357753796553, 'locationFinished': True, 'longitude': -0.14189, 'positionType': 'GPS', 'locationType': None, 'latitude': 51.501364, 'isOld': False, 'horizontalAccuracy': 5.0}
>>> api.iphone.location()
{'timeStamp': 1357753796553, 'locationFinished': True, 'longitude': -0.14189, 'positionType': 'GPS', 'locationType': None, 'latitude': 51.501364, 'isOld': False, 'horizontalAccuracy': 5.0}
Status Status
****** ******
The Find My iPhone response is quite bloated, so for simplicity's sake this method will return a subset of the properties. 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() .. code-block:: pycon
{'deviceDisplayName': 'iPhone 5', 'deviceStatus': '200', 'batteryLevel': 0.6166913, 'name': "Peter's iPhone"}
>>> api.iphone.status()
{'deviceDisplayName': 'iPhone 5', 'deviceStatus': '200', 'batteryLevel': 0.6166913, 'name': "Peter's iPhone"}
If you wish to request further properties, you may do so by passing in a list of property names. If you wish to request further properties, you may do so by passing in a list of property names.
@ -160,7 +178,9 @@ 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. 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() .. code-block:: python
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. 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.
@ -169,9 +189,11 @@ 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. 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' .. code-block:: python
>>> message = 'Thief! Return my phone immediately.'
>>> api.iphone.lost_device(phone_number, message) phone_number = '555-373-383'
message = 'Thief! Return my phone immediately.'
api.iphone.lost_device(phone_number, message)
Calendar Calendar
@ -184,17 +206,23 @@ Events
Returns this month's events: Returns this month's events:
>>> api.calendar.events() .. code-block:: python
api.calendar.events()
Or, between a specific date range: Or, between a specific date range:
>>> from_dt = datetime(2012, 1, 1) .. code-block:: python
>>> to_dt = datetime(2012, 1, 31)
>>> api.calendar.events(from_dt, to_dt) 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: Alternatively, you may fetch a single event's details, like so:
>>> api.calendar.get_event_detail('CALENDAR', 'EVENT_ID') .. code-block:: python
api.calendar.get_event_detail('CALENDAR', 'EVENT_ID')
Contacts Contacts
@ -202,9 +230,11 @@ Contacts
You can access your iCloud contacts/address book through the ``contacts`` property: You can access your iCloud contacts/address book through the ``contacts`` property:
>>> for c in api.contacts.all(): .. code-block:: pycon
>>> print c.get('firstName'), c.get('phones')
John [{'field': '+1 555-55-5555-5', 'label': 'MOBILE'}] >>> for c in api.contacts.all():
>>> print(c.get('firstName'), c.get('phones'))
John [{'field': '+1 555-55-5555-5', 'label': 'MOBILE'}]
Note: These contacts do not include contacts federated from e.g. Facebook, only the ones stored in iCloud. Note: These contacts do not include contacts federated from e.g. Facebook, only the ones stored in iCloud.
@ -214,93 +244,111 @@ File Storage (Ubiquity)
You can access documents stored in your iCloud account by using the ``files`` property's ``dir`` method: You can access documents stored in your iCloud account by using the ``files`` property's ``dir`` method:
>>> api.files.dir() .. code-block:: pycon
['.do-not-delete',
'.localized', >>> api.files.dir()
'com~apple~Notes', ['.do-not-delete',
'com~apple~Preview', '.localized',
'com~apple~mail', 'com~apple~Notes',
'com~apple~shoebox', 'com~apple~Preview',
'com~apple~system~spotlight' 'com~apple~mail',
] 'com~apple~shoebox',
'com~apple~system~spotlight'
]
You can access children and their children's children using the filename as an index: You can access children and their children's children using the filename as an index:
>>> api.files['com~apple~Notes'] .. code-block:: pycon
<Folder: 'com~apple~Notes'>
>>> api.files['com~apple~Notes'].type >>> api.files['com~apple~Notes']
'folder' <Folder: 'com~apple~Notes'>
>>> api.files['com~apple~Notes'].dir() >>> api.files['com~apple~Notes'].type
[u'Documents'] 'folder'
>>> api.files['com~apple~Notes']['Documents'].dir() >>> api.files['com~apple~Notes'].dir()
[u'Some Document'] ['Documents']
>>> api.files['com~apple~Notes']['Documents']['Some Document'].name >>> api.files['com~apple~Notes']['Documents'].dir()
u'Some Document' ['Some Document']
>>> api.files['com~apple~Notes']['Documents']['Some Document'].modified >>> api.files['com~apple~Notes']['Documents']['Some Document'].name
datetime.datetime(2012, 9, 13, 2, 26, 17) 'Some Document'
>>> api.files['com~apple~Notes']['Documents']['Some Document'].size >>> api.files['com~apple~Notes']['Documents']['Some Document'].modified
1308134 datetime.datetime(2012, 9, 13, 2, 26, 17)
>>> api.files['com~apple~Notes']['Documents']['Some Document'].type >>> api.files['com~apple~Notes']['Documents']['Some Document'].size
u'file' 1308134
>>> api.files['com~apple~Notes']['Documents']['Some Document'].type
'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``. 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 .. code-block:: pycon
'Hello, these are the file contents'
>>> api.files['com~apple~Notes']['Documents']['Some Document'].open().content
'Hello, these are the file contents'
Note: the object returned from the above ``open`` method is a `response object <http://www.python-requests.org/en/latest/api/#classes>`_ and the ``open`` method can accept any parameters you might normally use in a request using `requests <https://github.com/kennethreitz/requests>`_. Note: the object returned from the above ``open`` method is a `response object <http://www.python-requests.org/en/latest/api/#classes>`_ and the ``open`` method can accept any parameters you might normally use in a request using `requests <https://github.com/kennethreitz/requests>`_.
For example, if you know that the file you're opening has JSON content: For example, if you know that the file you're opening has JSON content:
>>> api.files['com~apple~Notes']['Documents']['information.json'].open().json() .. code-block:: pycon
{'How much we love you': 'lots'}
>>> api.files['com~apple~Notes']['Documents']['information.json'].open().json()['How much we love you'] >>> api.files['com~apple~Notes']['Documents']['information.json'].open().json()
'lots' {'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: 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) .. code-block:: pycon
>>> with open('downloaded_file.zip', 'wb') as opened_file:
opened_file.write(download.raw.read()) >>> 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) 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```: 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() .. code-block:: pycon
['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'] >>> api.drive.dir()
>>> drive_file.name ['Holiday Photos', 'Work Files']
u'DSC08116.JPG' >>> api.drive['Holiday Photos']['2013']['Sicily'].dir()
>>> drive_file.date_modified ['DSC08116.JPG', 'DSC08117.JPG']
datetime.datetime(2013, 3, 21, 12, 28, 12) # NB this is UTC
>>> drive_file.size >>> drive_file = api.drive['Holiday Photos']['2013']['Sicily']['DSC08116.JPG']
2021698 >>> drive_file.name
>>> drive_file.type 'DSC08116.JPG'
u'file' >>> drive_file.date_modified
datetime.datetime(2013, 3, 21, 12, 28, 12) # NB this is UTC
>>> drive_file.size
2021698
>>> drive_file.type
'file'
The ``open`` method will return a response object from which you can read the file's contents: The ``open`` method will return a response object from which you can read the file's contents:
>>> from shutil import copyfileobj .. code-block:: python
>>> with drive_file.open(stream=True) as response:
>>> with open(drive_file.name, 'wb') as file_out: from shutil import copyfileobj
>>> copyfileobj(response.raw, file_out) 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 To interact with files and directions the ``mkdir``, ``rename`` and ``delete`` functions are available
for a file or folder: for a file or folder:
>>> api.drive['Holiday Photos'].mkdir('2020') .. code-block:: python
>>> api.drive['Holiday Photos']['2020'].rename('2020_copy')
>>> api.drive['Holiday Photos']['2020_copy'].delete() 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: The ``upload`` method can be used to send a file-like object to the iCloud Drive:
>>> with open('Vacation.jpeg', 'rb') as file_in: .. code-block:: python
>>>> api.drive['Holiday Photos'].upload(file_in)
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 It is strongly suggested to open file handles as binary rather than text to prevent decoding errors
further down the line. further down the line.
@ -310,38 +358,50 @@ Photo Library
You can access the iCloud Photo Library through the ``photos`` property. You can access the iCloud Photo Library through the ``photos`` property.
>>> api.photos.all .. code-block:: pycon
<PhotoAlbum: 'All Photos'>
>>> api.photos.all
<PhotoAlbum: 'All Photos'>
Individual albums are available through the ``albums`` property: Individual albums are available through the ``albums`` property:
>>> api.photos.albums['Screenshots'] .. code-block:: pycon
<PhotoAlbum: 'Screenshots'>
>>> 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) : 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']: .. code-block:: pycon
print photo, photo.filename
<PhotoAsset: id=AVbLPCGkp798nTb9KZozCXtO7jds> IMG_6045.JPG >>> 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 <http://www.python-requests.org/en/latest/api/#classes>`_, initialized with ``stream`` set to ``True``, so you can read from the raw response object: To download a photo use the `download` method, which will return a `response object <http://www.python-requests.org/en/latest/api/#classes>`_, initialized with ``stream`` set to ``True``, so you can read from the raw response object:
>>> photo = next(iter(api.photos.albums['Screenshots']), None) .. code-block:: python
>>> download = photo.download()
>>> with open(photo.filename, 'wb') as opened_file: 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()) 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. 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: Information about each version can be accessed through the ``versions`` property:
>>> photo.versions.keys() .. code-block:: pycon
['medium', 'original', 'thumb']
>>> photo.versions.keys()
['medium', 'original', 'thumb']
To download a specific version of the photo asset, pass the version to ``download()``: To download a specific version of the photo asset, pass the version to ``download()``:
>>> download = photo.download('thumb') .. code-block:: python
>>> with open(photo.versions['thumb']['filename'], 'wb') as thumb_file:
download = photo.download('thumb')
with open(photo.versions['thumb']['filename'], 'wb') as thumb_file:
thumb_file.write(download.raw.read()) thumb_file.write(download.raw.read())