135 lines
4.1 KiB
Python
135 lines
4.1 KiB
Python
#!/usr/bin/env python3
|
|
import configparser
|
|
import logging
|
|
import sys
|
|
import urllib.parse
|
|
|
|
import requests
|
|
|
|
from pyicloud import PyiCloudService
|
|
from requests.auth import HTTPBasicAuth
|
|
|
|
|
|
def load_config(config_str=None):
|
|
"""
|
|
Load and parse config from string provided. Defaults to reading from stdin.
|
|
"""
|
|
if not config_str:
|
|
config_str = sys.stdin.read()
|
|
config = configparser.ConfigParser()
|
|
config.read_string(config_str)
|
|
return config
|
|
|
|
|
|
def get_icloud_location(config):
|
|
"""
|
|
Fetch latest iPhone location from iCloud
|
|
"""
|
|
email = config['apple']['email']
|
|
password = config['apple']['password']
|
|
api = PyiCloudService(email, password)
|
|
|
|
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)
|
|
|
|
iphone = next(
|
|
device
|
|
for device in api.devices
|
|
if 'iPhone' in device.status()['name']
|
|
)
|
|
iphone_location = iphone.location()
|
|
iphone_status = iphone.status()
|
|
|
|
return iphone_location, iphone_status
|
|
|
|
|
|
def store_location_in_nextcloud(config, iphone_location, iphone_status):
|
|
"""
|
|
Store provided iPhone location to Nextcloud.
|
|
"""
|
|
nextcloud_location_args = {
|
|
"user_agent": iphone_status['name'],
|
|
"lat": iphone_location['latitude'],
|
|
"lng": iphone_location['longitude'],
|
|
"accuracy": iphone_location['horizontalAccuracy'],
|
|
"timestamp": iphone_location['timeStamp'] // 1000,
|
|
"altitude": iphone_location['altitude'],
|
|
"battery": iphone_status['batteryLevel'],
|
|
}
|
|
logging.info('Got location data from iCloud: %s.', nextcloud_location_args)
|
|
logging.debug(
|
|
"curl -X POST -u '%s:%s' '%s'",
|
|
config['nextcloud']['user'],
|
|
config['nextcloud']['password'],
|
|
(
|
|
'%s?%s' % (
|
|
urllib.parse.urljoin(
|
|
config['nextcloud']['server'], '/apps/maps/api/1.0/devices'
|
|
),
|
|
urllib.parse.urlencode(nextcloud_location_args),
|
|
)
|
|
),
|
|
)
|
|
r = requests.post(
|
|
urllib.parse.urljoin(
|
|
config['nextcloud']['server'], '/apps/maps/api/1.0/devices'
|
|
),
|
|
params=nextcloud_location_args,
|
|
auth=HTTPBasicAuth(
|
|
config['nextcloud']['user'], config['nextcloud']['password']
|
|
)
|
|
)
|
|
r.raise_for_status()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
logging.basicConfig(level=logging.INFO)
|
|
config = load_config()
|
|
iphone_location, iphone_status = get_icloud_location(config)
|
|
store_location_in_nextcloud(config, iphone_location, iphone_status)
|