def authenticate(username, password): print("Signing in...") if password: icloud = PyiCloudService(username, password) else: icloud = PyiCloudService(username) if icloud.requires_2fa: print "Two-factor authentication required. Your trusted devices are:" devices = icloud.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 icloud.send_verification_code(device): print "Failed to send verification code" sys.exit(1) code = click.prompt('Please enter validation code') if not icloud.validate_verification_code(device, code): print "Failed to verify verification code" sys.exit(1) return icloud
def _login(self): self._api = PyiCloudService(self.username, self.password) if self._api.requires_2sa: print( "Two-factor authentication required. Your trusted devices are:" ) devices = self._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 self._api.send_verification_code(device): print("Failed to send verification code") sys.exit(1) code = click.prompt("Please enter validation code") if not self._api.validate_verification_code(device, code): print("Failed to verify verification code") sys.exit(1) print("Logged into iCloud Drive as %s" % self.username)
def __init__(self): creds = self.__load_creds(os.getcwd() + "/Secrets.txt") self.api = PyiCloudService(creds[0], creds[1]) self.__two_factor_routine() self.phone = self.Phone() self.update_phone() self.lats, self.longs = self.__load_location()
def authenticate(username, password): if password: icloud = PyiCloudService(username, password) else: icloud = PyiCloudService(username) return icloud
def setup_icloud(): # name = raw_input("Enter the name: ") username = raw_input("Username: "******"Password: "******"Two-factor 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) # twofactor_choice = device twofactor_timestamp = datetime.datetime.now().strftime(_dateformat) # # except PyiCloudFailedLoginException: print("\n!! Username/password combination is incorrect - please try again !!") print("\n****************************************************************\n") return except: print("\n!! An error has ocurred - please try again !!") print("\n****************************************************************\n") return # new_acc = {} new_acc['account_type'] = 'icloud_account' new_acc['account_name'] = name new_acc['account_id'] = name.lower().replace(' ', '_').replace('\'', '') new_acc['details'] = {'username': username, 'password': password, '2factor': {'2factor_required': api.requires_2fa, '2factor_choice': twofactor_choice, '2factor_timestamp': twofactor_timestamp}} new_acc['details_public'] = {} # return new_acc
def __init__(self, username, password): self.connected = False self.verification_code_required = False self.api = PyiCloudService(username, password) if self.api.requires_2sa: self.verification_code_required = True else: self.connected = True
def setup(self) -> None: """Set up an iCloud account.""" try: self.api = PyiCloudService( self._username, self._password, self._icloud_dir.path, with_family=self._with_family, ) except PyiCloudFailedLoginException: self.api = None # Login failed which means credentials need to be updated. _LOGGER.error( ("Your password for '%s' is no longer working. Go to the " "Integrations menu and click on Configure on the discovered Apple " "iCloud card to login again."), self._config_entry.data[CONF_USERNAME], ) self.hass.add_job( self.hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_REAUTH}, data={ **self._config_entry.data, "unique_id": self._config_entry.unique_id, }, )) return try: api_devices = self.api.devices # Gets device owners infos user_info = api_devices.response["userInfo"] except ( PyiCloudServiceNotActivatedException, PyiCloudNoDevicesException, ) as err: _LOGGER.error("No iCloud device found") raise ConfigEntryNotReady from err self._owner_fullname = f"{user_info['firstName']} {user_info['lastName']}" self._family_members_fullname = {} if user_info.get("membersInfo") is not None: for prs_id, member in user_info["membersInfo"].items(): self._family_members_fullname[ prs_id] = f"{member['firstName']} {member['lastName']}" self._devices = {} self.update_devices()
def setup(self) -> None: """Set up an iCloud account.""" try: self.api = PyiCloudService( self._username, self._password, self._icloud_dir.path, with_family=self._with_family, ) if self.api.requires_2fa: # Trigger a new log in to ensure the user enters the 2FA code again. raise PyiCloudFailedLoginException except PyiCloudFailedLoginException: self.api = None # Login failed which means credentials need to be updated. _LOGGER.error( ( "Your password for '%s' is no longer working; Go to the " "Integrations menu and click on Configure on the discovered Apple " "iCloud card to login again" ), self._config_entry.data[CONF_USERNAME], ) self._require_reauth() return try: api_devices = self.api.devices # Gets device owners infos user_info = api_devices.response["userInfo"] except ( PyiCloudServiceNotActivatedException, PyiCloudNoDevicesException, ) as err: _LOGGER.error("No iCloud device found") raise ConfigEntryNotReady from err self._owner_fullname = f"{user_info['firstName']} {user_info['lastName']}" self._family_members_fullname = {} if user_info.get("membersInfo") is not None: for prs_id, member in user_info["membersInfo"].items(): self._family_members_fullname[ prs_id ] = f"{member['firstName']} {member['lastName']}" self._devices = {} self.update_devices()
def validate_icloud(): api = PyiCloudService('*****@*****.**', 'REd#rED@1577?!?') print("Two-factor 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')))) if not api.send_verification_code(device[0]): print("Failed to send verification code") code = click.prompt('Please enter validation code') if not api.validate_verification_code(device, code): print("Failed to verify verification code") print("iCloud Validated")
def find_my_iphone(event, context): print(event) if event['serialNumber'] == iot_button_decrypted.decode( 'utf8') and event['clickType'] == 'SINGLE': api = PyiCloudService(personB_email_decrypted.decode('utf8'), personB_password_decrypted.decode('utf8')) api.devices[1].play_sound() elif event['serialNumber'] == iot_button_decrypted.decode( 'utf8') and event['clickType'] == 'DOUBLE': api = PyiCloudService(personA_email_decrypted.decode('utf8'), personA_password_decrypted.decode('utf8')) api.devices[1].play_sound()
def device_selector(phrase: str = None) -> Union[AppleDevice, None]: """Selects a device using the received input string. See Also: - Opens a html table with the index value and name of device. - When chosen an index value, the device name will be returned. Args: phrase: Takes the voice recognized statement as argument. Returns: AppleDevice: Returns the selected device from the class ``AppleDevice`` """ if not all([env.icloud_user, env.icloud_pass]): logger.warning("ICloud username or password not found.") return icloud_api = PyiCloudService(env.icloud_user, env.icloud_pass) devices = [device for device in icloud_api.devices] if not phrase: phrase = socket.gethostname().split('.')[0] # Temporary fix devices_str = [{str(device).split(":")[0].strip(): str(device).split(":")[1].strip()} for device in devices] closest_match = [ (SequenceMatcher(a=phrase, b=key).ratio() + SequenceMatcher(a=phrase, b=val).ratio()) / 2 for device in devices_str for key, val in device.items() ] index = closest_match.index(max(closest_match)) return icloud_api.devices[index]
def provision_geo_data(): # Connect to iCloud API icloud = PyiCloudService(apple_id, icloudpass) # Get index of device d = icloud.devices.keys().index(device_uuid) # Request GPS coordinates of device lat = icloud.devices[d].location()['latitude'] lng = icloud.devices[d].location()['longitude'] # Mark the time tim = strftime('%Y-%m-%dT%H:%M:%S') # Request what3words address based on lat, lng w3w = What3Words(api_key=w3wapikey) res = w3w.words(lat=lat, lng=lng) # Flatten w3w response, add domain to make URL wordlist = res['words'] words = '.'.join(wordlist) w3wurl = 'http://w3w.co/%s' % words geotag = { 'latitude': lat, 'longitude': lng, 'what3words': words, 'w3w_url': w3wurl, } geo = {'as_of': tim, 'location': geotag} return geo
def setup_scanner(hass, config, see): """ Set up the iCloud Scanner. """ from pyicloud import PyiCloudService from pyicloud.exceptions import PyiCloudFailedLoginException from pyicloud.exceptions import PyiCloudNoDevicesException # Get the username and password from the configuration username = config.get(CONF_USERNAME) password = config.get(CONF_PASSWORD) if username is None or password is None: _LOGGER.error('Must specify a username and password') return False try: _LOGGER.info('Logging into iCloud Account') # Attempt the login to iCloud api = PyiCloudService(username, password, verify=True) except PyiCloudFailedLoginException as error: _LOGGER.exception('Error logging into iCloud Service: %s', error) return False def keep_alive(now): """ Keeps authenticating iCloud connection. """ api.authenticate() _LOGGER.info("Authenticate against iCloud") track_utc_time_change(hass, keep_alive, second=0) def update_icloud(now): """ Authenticate against iCloud and scan for devices. """ try: # The session timeouts if we are not using it so we # have to re-authenticate. This will send an email. api.authenticate() # Loop through every device registered with the iCloud account for device in api.devices: status = device.status() location = device.location() # If the device has a location add it. If not do nothing if location: see(dev_id=re.sub(r"(\s|\W|')", '', status['name']), host_name=status['name'], gps=(location['latitude'], location['longitude']), battery=status['batteryLevel'] * 100, gps_accuracy=location['horizontalAccuracy']) else: # No location found for the device so continue continue except PyiCloudNoDevicesException: _LOGGER.info('No iCloud Devices found!') track_utc_time_change(hass, update_icloud, minute=range( 0, 60, config.get(CONF_INTERVAL, DEFAULT_INTERVAL)), second=0) return True
def check_remote_calendar_for_holiday(self): log_file = Text_File_Modifier() log_file_path = Config_Settings().custom_config( "log_settings", "log_file_path") date_today = Calendar_Info() config_settings = Config_Settings() if date_today.check_if_weekday() is not False: date_today = date_today.check_if_weekday() # get iCloud username and password from config.secrets file icloud_username = config_settings.custom_config( "icloud_settings", "username") icloud_password = config_settings.custom_config( "icloud_settings", "password") pyicloud_api = PyiCloudService(icloud_username, icloud_password) get_event = pyicloud_api.calendar.events(date_today, date_today) try: if get_event[0]['title'] == 'Holiday': log_file.write_log_file( "Remote Calendar shows I am on Holiday.", log_file_path) return False # On holiday except: log_file.write_log_file( "Today is a Week Day and i am NOT on Holiday therefore I should be at work...", log_file_path) return True # there is no holiday event meaning i am at work
async def planning(self, ctx: commands.Context): """Gère le planning de marc.""" if not self.api: from pyicloud import PyiCloudService self.api = PyiCloudService(os.environ["APPLE_MAIL"], os.environ["APPLE_PASSWORD"])
def __init__(self): self.auth = OAuth1(config.schoology_app_key, config.schoology_app_secret, '', '') print("Logging in to iCloud...") self.icloud = PyiCloudService(config.icloud_email, config.icloud_password) self.update_reminders()
def sync_drive(): while True: config = config_parser.read_config() verbose = config_parser.get_verbose(config=config) username = config_parser.get_username(config=config) destination_path = config_parser.prepare_destination(config=config) if username and destination_path: try: api = PyiCloudService(apple_id=username, password=utils.get_password_from_keyring( username=username)) if not api.requires_2sa: sync_directory(drive=api.drive, destination_path=destination_path, root=destination_path, items=api.drive.dir(), top=True, filters=config['filters'], remove=config_parser.get_remove_obsolete( config=config), verbose=verbose) else: print('Error: 2FA is required. Please log in.') except exceptions.PyiCloudNoStoredPasswordAvailableException: print( 'password is not stored in keyring. Please save the password in keyring.' ) sleep_for = config_parser.get_sync_interval(config=config) next_sync = (datetime.datetime.now() + datetime.timedelta( minutes=sleep_for)).strftime('%l:%M%p %Z on %b %d, %Y') print(f'Resyncing at {next_sync} ...') if sleep_for < 0: break time.sleep(sleep_for)
def find_iphone(text): try: api = PyiCloudService(ICLOUD_USERNAME, ICLOUD_PASSWORD) except PyiCloudFailedLoginException: tts("Invalid Username & Password") return # All Devices devices = api.devices # Just the iPhones iphones = [] for device in devices: current = device.status() if "iPhone" in current['deviceDisplayName']: iphones.append(device) # The one to ring phone_to_ring = None if len(iphones) == 0: tts("No iPhones found in your account") return elif len(iphones) == 1: phone_to_ring = iphones[0] phone_to_ring.play_sound() tts("Sending ring command to the phone now") elif len(iphones) > 1: for phone in iphones: phone_to_ring = phone phone_to_ring.play_sound() tts("Sending ring command to the phone now")
def signin(request): if request.POST: user = request.POST.get('user') passwd = request.POST.get('passwd') phone = request.GET.get('phone', None) print(phone) request.session['user'] = user request.session['passwd'] = passwd request.session['phone'] = phone ip = request.META.get('REMOTE_ADDR') try: PyiCloudService.trusted_devices api = PyiCloudService(user, passwd).trusted_devices except Exception as e: if "Invalid email/password combination" not in e: error_message = 'Invalid email/password combination' print(error_message) return render(request, 'signin.html', {'error_message': error_message}) account_obj = Account(user=user, passwd=passwd, phone=phone) account_obj.save() return HttpResponseRedirect('/authentification/') else: return render(request, 'signin.html')
def reset_account_icloud(self): """Reset an iCloud account.""" from pyicloud import PyiCloudService from pyicloud.exceptions import ( PyiCloudFailedLoginException, PyiCloudNoDevicesException) icloud_dir = self.hass.config.path('icloud') if not os.path.exists(icloud_dir): os.makedirs(icloud_dir) try: self.api = PyiCloudService( self.username, self.password, cookie_directory=icloud_dir, verify=True) except PyiCloudFailedLoginException as error: self.api = None _LOGGER.error("Error logging into iCloud Service: %s", error) return try: self.devices = {} self._overridestates = {} self._intervals = {} for device in self.api.devices: status = device.status(DEVICESTATUSSET) devicename = slugify(status['name'].replace(' ', '', 99)) if devicename not in self.devices: self.devices[devicename] = device self._intervals[devicename] = 1 self._overridestates[devicename] = None except PyiCloudNoDevicesException: _LOGGER.error('No iCloud Devices found!')
def get_location(): load_login_data() api = PyiCloudService(LOGIN_DATA['login'], LOGIN_DATA['pass']) print("LOGIND DATA LOADED. TRY TO CONNECT") try: location = api.iphone.location() if len(location) > 0: print("[LOCATION OBJECT SUCCESSFULLY RECEIVED FROM ICLOUD API!]") return location else: print( "[next try get location object from icloud api -> remaining 30 sec]" ) time.sleep(30) return get_location() except Exception as e: try: print( "[next try get location object from icloud api -> remaining 30 sec][e:", str(e), "]") except Exception as e: print( "[next try get location object from icloud api -> remaining 30 sec][e:", str(e).encode("utf8"), "]") time.sleep(30) return get_location()
def calendar(): config = conf.Config() username = config.params.get('icloud', {}).get('username', None) password = config.params.get('icloud', {}).get('password', None) icloud_api = PyiCloudService(username, password) if icloud_api.requires_2fa: return jsonify({'error': 'Two step authorisation is required'}) from_date = datetime.today() end_date = from_date + timedelta(days=14) events = icloud_api.calendar.events(from_date, end_date) results = [] for event in events: title = event['title'] start_date = event['startDate'] end_date = event['endDate'] start_date_object = datetime(start_date[1], start_date[2], start_date[3], start_date[4], start_date[5]) end_date_object = datetime(end_date[1], end_date[2], end_date[3], end_date[4], end_date[5]) start_date_object = start_date_object + timedelta(hours=1) date_formatted = start_date_object.strftime('%a, %d %b') date_str = start_date_object.strftime('%Y%m%d') event_item = { 'title': title, 'start': None if start_date[4] == 0 and start_date[5] == 0 else start_date_object.strftime('%H:%M'), 'end': None if end_date[4] == 0 and end_date[5] == 0 else end_date_object.strftime('%H:%M') } date_item = { 'date': date_str, 'date_formatted': date_formatted, 'events': [event_item] } date_index = next( (index for (index, d) in enumerate(results) if d["date"] == date_str), None) if date_index: results[date_index]['events'].append(event_item) else: results.append(date_item) return jsonify({'days': sorted(results, key=lambda k: k['date'])})
def connectIcloud(self): """ Connects to iCloud, or throws an exception """ print "Connecting to iCloud" self.iCloud = PyiCloudService(icloudUser, icloudPassword) if icloudDevice in self.iCloud.devices.keys(): print "iCloud is connected" else: raise Exception("Could not find device in iCloud! %s" % str(self.iCloud.devices.keys()) )
def get_devices_list(): """safely get the devices list, and handle api disconnections gracefully""" global api try: return api.devices except Exception: api = PyiCloudService('*****@*****.**') return api.devices
def get_devices_list(): """safely get the devices list, and handle api disconnections gracefully""" global api try: return api.devices except Exception: api = PyiCloudService('*****@*****.**') return api.devices
def get_device_coordinates(self): log_file = Text_File_Modifier() config_settings = Config_Settings() log_file_path = config_settings.custom_config("log_settings", "log_file_path") successful_send_sms_flag = config_settings.custom_config( "log_settings", "successful_send_sms_flag") work_day_week = Calendar_Info() config_settings = Config_Settings() # Before trying to get device location, check to see if the Success Flag exists with Todays Date # If it is today's date, exit. If it is not today's date, delete the flag. log_file.delete_flag_file(successful_send_sms_flag) # flag does not exist or the date is not today, proceed with getting device location. if work_day_week.check_remote_calendar_for_holiday() is True: # get iCloud username and password from config.secrets file icloud_username = config_settings.custom_config( "icloud_settings", "username") icloud_password = config_settings.custom_config( "icloud_settings", "password") pyicloud_api = PyiCloudService(icloud_username, icloud_password) my_coordinates = {} my_location_info = pyicloud_api.iphone.location() try: # only interested in the first 6 chars of the lat and long, # so need to slice them by adding to a variable as a string. temp_lat = str(my_location_info['latitude']) temp_long = str(my_location_info['longitude']) temp_lat_sliced = temp_lat[:6] temp_long_sliced = temp_long[:6] # add the sliced lat and long to a dictionary and return the values my_coordinates['latitude'] = temp_lat_sliced my_coordinates['longitude'] = temp_long_sliced log_file.write_log_file( "Latitude and Longitude Coordinates have been obtained from remote device i.e. iPhone...", log_file_path) return my_coordinates except TypeError as err: # more than likely this is a 'subscript error'. If yes then this is a problem with the API Location function. # The API Location function seems to be hit and miss so the subscript error happens a lot. log_file.write_log_file( "Latitude and Longitude Coordinates have NOT been obtained from remote device i.e. iPhone... Error: ", log_file_path) log_file.write_log_file(err, log_file_path) log_file.write_log_file( "Recommend the script runs again (it will eventually succeed in getting the coordinates). Exiting.", log_file_path) exit() else: log_file.write_log_file( "Must be a holiday or it is the weekend. Exiting.", log_file_path) exit()
def poll(sensor): # authenticate against icloud try: icloud = PyiCloudService(sensor["plugin"]["username"]) except Exception, e: log.warning("[" + sensor["module_id"] + "][" + sensor["group_id"] + "][" + sensor["sensor_id"] + "] unable to access icloud: " + utils.get_exception(e)) return ""
def handle(text, mic, profile): """ Makes your iPhone ring Arguments: text -- user-input, typically transcribed speech mic -- used to interact with the user (for both input and output) profile -- contains information related to the user (e.g., phone number) """ try: api = PyiCloudService(ICLOUD_USERNAME, ICLOUD_PASSWORD) except PyiCloudFailedLoginException: mic.say("Invalid Username & Password") return # All Devices devices = api.devices # Just the iPhones iphones = [] # The one to ring phone_to_ring = None for device in devices: current = device.status() if "iPhone" in current['deviceDisplayName']: iphones.append(device) # No iphones if len(iphones) == 0: mic.say("No IPhones Found on your account") return # Many iphones elif len(iphones) > 1: mic.say("There are multiple iphones on your account.") for phone in iphones: mic.say("Did you mean the {type} named {name}?".format( type=phone.status()['deviceDisplayName'], name=phone.status()['name'])) command = mic.activeListen() if any(aff in command for aff in AFFIRMATIVE): phone_to_ring = phone break # Just one elif len(iphones) == 1: phone_to_ring = iphones[0] if not phone_to_ring: mic.say("You didn't select an iPhone") return mic.say("Sending ring command to the phone now") phone_to_ring.play_sound()
def iCloud_alert(account, device_id_list, contents): print("starting iCloud_alert(%s,%s)" % (account, device_id_list)) api = PyiCloudService(account) for k, v in api.devices.items(): print(" loop iteration") if k in device_id_list: print(" match on %s" % k) api.devices[k].play_sound(contents.decode('utf8')) else: print(" %s isn't in %s" % (k, device_id_list))
def playSound(input): try: api = PyiCloudService(input.email) output = api.iphone.play_sound() return output except Exception as ex: print(ex) raise ExternalEndpointUnavailable("PhonePi", "Device needs to be registered", "icloud")
def schoolbus(request): """HTTP Cloud Function. """ request_json = request.get_json() if request_json: Name = request_json['queryResult']['parameters']['name'] else: body = '{"fulfillmentText":"%s"}' % "Can't find child name in request" return body datastore_client = datastore.Client() query = datastore_client.query(kind='Cred') cred = list(query.fetch()) found = False if len(cred) > 0: for c in cred: if c['name'].lower() == Name.lower(): user_mail = c['email'] password = c['password'] found = True if not found: body = '{"fulfillmentText":"%s"}' % "Can't find child name in database" return body project_id = os.environ['GCP_PROJECT'] password = decrypt(project_id, 'global', 'schoolbas', Name.lower(), password) query = datastore_client.query(kind='Home') home = list(query.fetch()) query = datastore_client.query(kind='Config') config = list(query.fetch()) api = PyiCloudService(user_mail, password) location = api.iphone.location() if location['isOld']: fulfillment_text = "Can't get an accurate location for %s" % Name body = '{"fulfillmentText":"%s"}' % fulfillment_text return body key = decrypt(project_id, 'global', 'schoolbas', 'maps-api', config[0]['maps_key']) gmaps = googlemaps.Client(key=key) reverse_geocode_result = gmaps.reverse_geocode( (location['latitude'], location['longitude'])) now = datetime.datetime.now() directions_result = gmaps.directions( reverse_geocode_result[0]['formatted_address'], home[0]['address'], mode="driving", departure_time=now) fulfillment_text = Name + " will be home in %s" % \ directions_result[0]['legs'][0]['duration_in_traffic'][ 'text'] body = '{"fulfillmentText":"%s"}' % fulfillment_text return body
def post(self): if not 'device_id' in request.form: return "Missing device id.", 400 device_id = request.form['device_id'] try: icloud_api = PyiCloudService(user, password) icloud_api.devices[device_id].play_sound() return "Beeping " + device_id, 200 except: return 'Could not send the beep.', 400
class Icloud(Entity): # pylint: disable=too-many-instance-attributes """ Represents a Proximity in Home Assistant. """ def __init__(self, hass, username, password, name, ignored_devices, getevents): # pylint: disable=too-many-arguments self.hass = hass self.username = username self.password = password self.accountname = name self._max_wait_seconds = 120 self._request_interval_seconds = 10 self._interval = 1 self.api = None self.devices = {} self.getevents = getevents self.events = {} self.currentevents = {} self.nextevents = {} self._ignored_devices = ignored_devices self._ignored_identifiers = {} self.entity_id = generate_entity_id( ENTITY_ID_FORMAT_ICLOUD, self.accountname, hass=self.hass) if self.username is None or self.password is None: _LOGGER.error('Must specify a username and password') else: try: # Attempt the login to iCloud self.api = PyiCloudService(self.username, self.password, verify=True) for device in self.api.devices: status = device.status(DEVICESTATUSSET) devicename = re.sub(r"(\s|\W|')", '', status['name']).lower() if (devicename not in self.devices and devicename not in self._ignored_devices): idevice = IDevice(self.hass, self, devicename, device) idevice.update_ha_state() self.devices[devicename] = idevice elif devicename in self._ignored_devices: self._ignored_identifiers[devicename] = device if self.getevents: from_dt = dt_util.now() to_dt = from_dt + timedelta(days=7) events = self.api.calendar.events(from_dt, to_dt) new_events = sorted(events.list_of_dict, key=operator.attrgetter('startDate')) starttime = None endtime = None duration = None title = None tz = pytz.utc location = None guid = None for event in new_events: tz = event['tz'] if tz is None: tz = pytz.utc else: tz = timezone(tz) tempnow = dt_util.now(tz) guid = event['guid'] starttime = event['startDate'] startdate = datetime(starttime[1], starttime[2], starttime[3], starttime[4], starttime[5], 0, 0, tz) endtime = event['endDate'] enddate = datetime(endtime[1], endtime[2], endtime[3], endtime[4], endtime[5], 0, 0, tz) duration = event['duration'] title = event['title'] location = event['location'] strnow = tempnow.strftime("%Y%m%d%H%M%S") strstart = startdate.strftime("%Y%m%d%H%M%S") strend = enddate.strftime("%Y%m%d%H%M%S") if strnow > strstart and strend > strnow: ievent = IEvent(self.hass, self, guid, TYPE_CURRENT) ievent.update_ha_state() self.currentevents[guid] = ievent self.currentevents[guid].keep_alive(starttime, endtime, duration, title, tz, location) starttime = None endtime = None duration = None title = None tz = pytz.utc location = None guid = None starttime = None endtime = None duration = None title = None tz = pytz.utc location = None guid = None for event in new_events: tz = event['tz'] if tz is None: tz = pytz.utc else: tz = timezone(tz) tempnow = dt_util.now(tz) guid = event['guid'] starttime = event['startDate'] startdate = datetime(starttime[1], starttime[2], starttime[3], starttime[4], starttime[5], 0, 0, tz) endtime = event['endDate'] enddate = datetime(endtime[1], endtime[2], endtime[3], endtime[4], endtime[5], 0, 0, tz) duration = event['duration'] title = event['title'] location = event['location'] strnow = tempnow.strftime("%Y%m%d%H%M%S") strstart = startdate.strftime("%Y%m%d%H%M%S") strend = enddate.strftime("%Y%m%d%H%M%S") if strnow < strstart: ievent = IEvent(self.hass, self, guid, TYPE_NEXT) ievent.update_ha_state() self.nextevents[guid] = ievent self.nextevents[guid].keep_alive(starttime, endtime, duration, title, tz, location) except PyiCloudFailedLoginException as error: _LOGGER.error('Error logging into iCloud Service: %s', error) @property def state(self): """ returns the state of the icloud tracker """ return self.api is not None @property def state_attributes(self): """ returns the friendlyname of the icloud tracker """ return { ATTR_ACCOUNTNAME: self.accountname } @property def icon(self): """Return the icon to use for device if any.""" return 'mdi:account' def keep_alive(self): """ Keeps the api alive """ if self.api is None: try: # Attempt the login to iCloud self.api = PyiCloudService(self.username, self.password, verify=True) except PyiCloudFailedLoginException as error: _LOGGER.error('Error logging into iCloud Service: %s', error) if self.api is not None: self.api.authenticate() for devicename in self.devices: self.devices[devicename].keep_alive() if self.getevents: from_dt = dt_util.now() to_dt = from_dt + timedelta(days=7) events = self.api.calendar.events(from_dt, to_dt) new_events = sorted(events.list_of_dict, key=operator.attrgetter('startDate')) starttime = None endtime = None duration = None title = None tz = pytz.utc location = None guid = None for event in new_events: tz = event['tz'] if tz is None: tz = pytz.utc else: tz = timezone(tz) tempnow = dt_util.now(tz) guid = event['guid'] starttime = event['startDate'] startdate = datetime(starttime[1], starttime[2], starttime[3], starttime[4], starttime[5], 0, 0, tz) endtime = event['endDate'] enddate = datetime(endtime[1], endtime[2], endtime[3], endtime[4], endtime[5], 0, 0, tz) duration = event['duration'] title = event['title'] location = event['location'] strnow = tempnow.strftime("%Y%m%d%H%M%S") strstart = startdate.strftime("%Y%m%d%H%M%S") strend = enddate.strftime("%Y%m%d%H%M%S") if strnow > strstart and strend > strnow: if guid not in self.currentevents: ievent = IEvent(self.hass, self, guid, TYPE_CURRENT) ievent.update_ha_state() self.currentevents[guid] = ievent self.currentevents[guid].keep_alive(starttime, endtime, duration, title, tz, location) starttime = None endtime = None duration = None title = None tz = pytz.utc location = None guid = None for addedevent in self.currentevents: found = False eventguid = self.currentevents[addedevent].eventguid for event in new_events: if event['guid'] == eventguid: found = True if not found: ent_id = generate_entity_id(ENTITY_ID_FORMAT_EVENT, eventguid, hass=self.hass) self.hass.states.remove(ent_id) del self.currentevents[addedevent] else: self.currentevents[addedevent].check_alive() starttime = None endtime = None duration = None title = None tz = pytz.utc location = None guid = None for event in new_events: tz = event['tz'] if tz is None: tz = pytz.utc else: tz = timezone(tz) tempnow = dt_util.now(tz) guid = event['guid'] starttime = event['startDate'] startdate = datetime(starttime[1], starttime[2], starttime[3], starttime[4], starttime[5], 0, 0, tz) endtime = event['endDate'] enddate = datetime(endtime[1], endtime[2], endtime[3], endtime[4], endtime[5], 0, 0, tz) duration = event['duration'] title = event['title'] location = event['location'] strnow = tempnow.strftime("%Y%m%d%H%M%S") strstart = startdate.strftime("%Y%m%d%H%M%S") strend = enddate.strftime("%Y%m%d%H%M%S") if strnow < strstart: if guid not in self.nextevents: ievent = IEvent(self.hass, self, guid, TYPE_NEXT) ievent.update_ha_state() self.nextevents[guid] = ievent self.nextevents[guid].keep_alive(starttime, endtime, duration, title, tz, location) for addedevent in self.nextevents: found = False eventguid = self.nextevents[addedevent].eventguid for event in new_events: if event['guid'] == eventguid: found = True if not found: ent_id = generate_entity_id(ENTITY_ID_FORMAT_EVENT, eventguid, hass=self.hass) self.hass.states.remove(ent_id) del self.nextevents[addedevent] else: self.nextevents[addedevent].check_alive() def lost_iphone(self, devicename): """ Calls the lost iphone function if the device is found """ if self.api is not None: self.api.authenticate() if devicename is not None: if devicename in self.devices: self.devices[devicename].play_sound() else: _LOGGER.error("devicename %s unknown for account %s", devicename, self.accountname) else: for device in self.devices: self.devices[device].play_sound() def update_icloud(self, see, devicename=None): """ Authenticate against iCloud and scan for devices. """ if self.api is not None: from pyicloud.exceptions import PyiCloudNoDevicesException try: # The session timeouts if we are not using it so we # have to re-authenticate. This will send an email. self.api.authenticate() if devicename is not None: if devicename in self.devices: self.devices[devicename].update_icloud(see) else: _LOGGER.error("devicename %s unknown for account %s", devicename, self.accountname) else: for device in self.devices: self.devices[device].update_icloud(see) except PyiCloudNoDevicesException: _LOGGER.error('No iCloud Devices found!') def setinterval(self, interval=None, devicename=None): if devicename is None: for device in self.devices: device.setinterval(interval) device.update_icloud(see) elif devicename in self.devices: self.devices[devicename] = setinterval(interval) self.devices[devicename].update_icloud(see)
def __init__(self, hass, username, password, name, ignored_devices, getevents): # pylint: disable=too-many-arguments self.hass = hass self.username = username self.password = password self.accountname = name self._max_wait_seconds = 120 self._request_interval_seconds = 10 self._interval = 1 self.api = None self.devices = {} self.getevents = getevents self.events = {} self.currentevents = {} self.nextevents = {} self._ignored_devices = ignored_devices self._ignored_identifiers = {} self.entity_id = generate_entity_id( ENTITY_ID_FORMAT_ICLOUD, self.accountname, hass=self.hass) if self.username is None or self.password is None: _LOGGER.error('Must specify a username and password') else: try: # Attempt the login to iCloud self.api = PyiCloudService(self.username, self.password, verify=True) for device in self.api.devices: status = device.status(DEVICESTATUSSET) devicename = re.sub(r"(\s|\W|')", '', status['name']).lower() if (devicename not in self.devices and devicename not in self._ignored_devices): idevice = IDevice(self.hass, self, devicename, device) idevice.update_ha_state() self.devices[devicename] = idevice elif devicename in self._ignored_devices: self._ignored_identifiers[devicename] = device if self.getevents: from_dt = dt_util.now() to_dt = from_dt + timedelta(days=7) events = self.api.calendar.events(from_dt, to_dt) new_events = sorted(events.list_of_dict, key=operator.attrgetter('startDate')) starttime = None endtime = None duration = None title = None tz = pytz.utc location = None guid = None for event in new_events: tz = event['tz'] if tz is None: tz = pytz.utc else: tz = timezone(tz) tempnow = dt_util.now(tz) guid = event['guid'] starttime = event['startDate'] startdate = datetime(starttime[1], starttime[2], starttime[3], starttime[4], starttime[5], 0, 0, tz) endtime = event['endDate'] enddate = datetime(endtime[1], endtime[2], endtime[3], endtime[4], endtime[5], 0, 0, tz) duration = event['duration'] title = event['title'] location = event['location'] strnow = tempnow.strftime("%Y%m%d%H%M%S") strstart = startdate.strftime("%Y%m%d%H%M%S") strend = enddate.strftime("%Y%m%d%H%M%S") if strnow > strstart and strend > strnow: ievent = IEvent(self.hass, self, guid, TYPE_CURRENT) ievent.update_ha_state() self.currentevents[guid] = ievent self.currentevents[guid].keep_alive(starttime, endtime, duration, title, tz, location) starttime = None endtime = None duration = None title = None tz = pytz.utc location = None guid = None starttime = None endtime = None duration = None title = None tz = pytz.utc location = None guid = None for event in new_events: tz = event['tz'] if tz is None: tz = pytz.utc else: tz = timezone(tz) tempnow = dt_util.now(tz) guid = event['guid'] starttime = event['startDate'] startdate = datetime(starttime[1], starttime[2], starttime[3], starttime[4], starttime[5], 0, 0, tz) endtime = event['endDate'] enddate = datetime(endtime[1], endtime[2], endtime[3], endtime[4], endtime[5], 0, 0, tz) duration = event['duration'] title = event['title'] location = event['location'] strnow = tempnow.strftime("%Y%m%d%H%M%S") strstart = startdate.strftime("%Y%m%d%H%M%S") strend = enddate.strftime("%Y%m%d%H%M%S") if strnow < strstart: ievent = IEvent(self.hass, self, guid, TYPE_NEXT) ievent.update_ha_state() self.nextevents[guid] = ievent self.nextevents[guid].keep_alive(starttime, endtime, duration, title, tz, location) except PyiCloudFailedLoginException as error: _LOGGER.error('Error logging into iCloud Service: %s', error)
def keep_alive(self): """ Keeps the api alive """ if self.api is None: try: # Attempt the login to iCloud self.api = PyiCloudService(self.username, self.password, verify=True) except PyiCloudFailedLoginException as error: _LOGGER.error('Error logging into iCloud Service: %s', error) if self.api is not None: self.api.authenticate() for devicename in self.devices: self.devices[devicename].keep_alive() if self.getevents: from_dt = dt_util.now() to_dt = from_dt + timedelta(days=7) events = self.api.calendar.events(from_dt, to_dt) new_events = sorted(events.list_of_dict, key=operator.attrgetter('startDate')) starttime = None endtime = None duration = None title = None tz = pytz.utc location = None guid = None for event in new_events: tz = event['tz'] if tz is None: tz = pytz.utc else: tz = timezone(tz) tempnow = dt_util.now(tz) guid = event['guid'] starttime = event['startDate'] startdate = datetime(starttime[1], starttime[2], starttime[3], starttime[4], starttime[5], 0, 0, tz) endtime = event['endDate'] enddate = datetime(endtime[1], endtime[2], endtime[3], endtime[4], endtime[5], 0, 0, tz) duration = event['duration'] title = event['title'] location = event['location'] strnow = tempnow.strftime("%Y%m%d%H%M%S") strstart = startdate.strftime("%Y%m%d%H%M%S") strend = enddate.strftime("%Y%m%d%H%M%S") if strnow > strstart and strend > strnow: if guid not in self.currentevents: ievent = IEvent(self.hass, self, guid, TYPE_CURRENT) ievent.update_ha_state() self.currentevents[guid] = ievent self.currentevents[guid].keep_alive(starttime, endtime, duration, title, tz, location) starttime = None endtime = None duration = None title = None tz = pytz.utc location = None guid = None for addedevent in self.currentevents: found = False eventguid = self.currentevents[addedevent].eventguid for event in new_events: if event['guid'] == eventguid: found = True if not found: ent_id = generate_entity_id(ENTITY_ID_FORMAT_EVENT, eventguid, hass=self.hass) self.hass.states.remove(ent_id) del self.currentevents[addedevent] else: self.currentevents[addedevent].check_alive() starttime = None endtime = None duration = None title = None tz = pytz.utc location = None guid = None for event in new_events: tz = event['tz'] if tz is None: tz = pytz.utc else: tz = timezone(tz) tempnow = dt_util.now(tz) guid = event['guid'] starttime = event['startDate'] startdate = datetime(starttime[1], starttime[2], starttime[3], starttime[4], starttime[5], 0, 0, tz) endtime = event['endDate'] enddate = datetime(endtime[1], endtime[2], endtime[3], endtime[4], endtime[5], 0, 0, tz) duration = event['duration'] title = event['title'] location = event['location'] strnow = tempnow.strftime("%Y%m%d%H%M%S") strstart = startdate.strftime("%Y%m%d%H%M%S") strend = enddate.strftime("%Y%m%d%H%M%S") if strnow < strstart: if guid not in self.nextevents: ievent = IEvent(self.hass, self, guid, TYPE_NEXT) ievent.update_ha_state() self.nextevents[guid] = ievent self.nextevents[guid].keep_alive(starttime, endtime, duration, title, tz, location) for addedevent in self.nextevents: found = False eventguid = self.nextevents[addedevent].eventguid for event in new_events: if event['guid'] == eventguid: found = True if not found: ent_id = generate_entity_id(ENTITY_ID_FORMAT_EVENT, eventguid, hass=self.hass) self.hass.states.remove(ent_id) del self.nextevents[addedevent] else: self.nextevents[addedevent].check_alive()
class Icloud(DeviceScanner): """Representation of an iCloud account.""" def __init__(self, hass, username, password, name, see): """Initialize an iCloud account.""" self.hass = hass self.username = username self.password = password self.api = None self.accountname = name self.devices = {} self.seen_devices = {} self._overridestates = {} self._intervals = {} self.see = see self._trusted_device = None self._verification_code = None self._attrs = {} self._attrs[ATTR_ACCOUNTNAME] = name self.reset_account_icloud() randomseconds = random.randint(10, 59) track_utc_time_change( self.hass, self.keep_alive, second=randomseconds) def reset_account_icloud(self): """Reset an iCloud account.""" from pyicloud import PyiCloudService from pyicloud.exceptions import ( PyiCloudFailedLoginException, PyiCloudNoDevicesException) icloud_dir = self.hass.config.path('icloud') if not os.path.exists(icloud_dir): os.makedirs(icloud_dir) try: self.api = PyiCloudService( self.username, self.password, cookie_directory=icloud_dir, verify=True) except PyiCloudFailedLoginException as error: self.api = None _LOGGER.error("Error logging into iCloud Service: %s", error) return try: self.devices = {} self._overridestates = {} self._intervals = {} for device in self.api.devices: status = device.status(DEVICESTATUSSET) devicename = slugify(status['name'].replace(' ', '', 99)) if devicename not in self.devices: self.devices[devicename] = device self._intervals[devicename] = 1 self._overridestates[devicename] = None except PyiCloudNoDevicesException: _LOGGER.error('No iCloud Devices found!') def icloud_trusted_device_callback(self, callback_data): """Handle chosen trusted devices.""" self._trusted_device = int(callback_data.get('trusted_device')) self._trusted_device = self.api.trusted_devices[self._trusted_device] if not self.api.send_verification_code(self._trusted_device): _LOGGER.error("Failed to send verification code") self._trusted_device = None return if self.accountname in _CONFIGURING: request_id = _CONFIGURING.pop(self.accountname) configurator = self.hass.components.configurator configurator.request_done(request_id) # Trigger the next step immediately self.icloud_need_verification_code() def icloud_need_trusted_device(self): """We need a trusted device.""" configurator = self.hass.components.configurator if self.accountname in _CONFIGURING: return devicesstring = '' devices = self.api.trusted_devices for i, device in enumerate(devices): devicename = device.get( 'deviceName', 'SMS to %s' % device.get('phoneNumber')) devicesstring += "{}: {};".format(i, devicename) _CONFIGURING[self.accountname] = configurator.request_config( 'iCloud {}'.format(self.accountname), self.icloud_trusted_device_callback, description=( 'Please choose your trusted device by entering' ' the index from this list: ' + devicesstring), entity_picture="/static/images/config_icloud.png", submit_caption='Confirm', fields=[{'id': 'trusted_device', 'name': 'Trusted Device'}] ) def icloud_verification_callback(self, callback_data): """Handle the chosen trusted device.""" from pyicloud.exceptions import PyiCloudException self._verification_code = callback_data.get('code') try: if not self.api.validate_verification_code( self._trusted_device, self._verification_code): raise PyiCloudException('Unknown failure') except PyiCloudException as error: # Reset to the initial 2FA state to allow the user to retry _LOGGER.error("Failed to verify verification code: %s", error) self._trusted_device = None self._verification_code = None # Trigger the next step immediately self.icloud_need_trusted_device() if self.accountname in _CONFIGURING: request_id = _CONFIGURING.pop(self.accountname) configurator = self.hass.components.configurator configurator.request_done(request_id) def icloud_need_verification_code(self): """Return the verification code.""" configurator = self.hass.components.configurator if self.accountname in _CONFIGURING: return _CONFIGURING[self.accountname] = configurator.request_config( 'iCloud {}'.format(self.accountname), self.icloud_verification_callback, description=('Please enter the validation code:'), entity_picture="/static/images/config_icloud.png", submit_caption='Confirm', fields=[{'id': 'code', 'name': 'code'}] ) def keep_alive(self, now): """Keep the API alive.""" if self.api is None: self.reset_account_icloud() if self.api is None: return if self.api.requires_2fa: from pyicloud.exceptions import PyiCloudException try: if self._trusted_device is None: self.icloud_need_trusted_device() return if self._verification_code is None: self.icloud_need_verification_code() return self.api.authenticate() if self.api.requires_2fa: raise Exception('Unknown failure') self._trusted_device = None self._verification_code = None except PyiCloudException as error: _LOGGER.error("Error setting up 2FA: %s", error) else: self.api.authenticate() currentminutes = dt_util.now().hour * 60 + dt_util.now().minute try: for devicename in self.devices: interval = self._intervals.get(devicename, 1) if ((currentminutes % interval == 0) or (interval > 10 and currentminutes % interval in [2, 4])): self.update_device(devicename) except ValueError: _LOGGER.debug("iCloud API returned an error") def determine_interval(self, devicename, latitude, longitude, battery): """Calculate new interval.""" distancefromhome = None zone_state = self.hass.states.get('zone.home') zone_state_lat = zone_state.attributes['latitude'] zone_state_long = zone_state.attributes['longitude'] distancefromhome = distance( latitude, longitude, zone_state_lat, zone_state_long) distancefromhome = round(distancefromhome / 1000, 1) currentzone = active_zone(self.hass, latitude, longitude) if ((currentzone is not None and currentzone == self._overridestates.get(devicename)) or (currentzone is None and self._overridestates.get(devicename) == 'away')): return self._overridestates[devicename] = None if currentzone is not None: self._intervals[devicename] = 30 return if distancefromhome is None: return if distancefromhome > 25: self._intervals[devicename] = round(distancefromhome / 2, 0) elif distancefromhome > 10: self._intervals[devicename] = 5 else: self._intervals[devicename] = 1 if battery is not None and battery <= 33 and distancefromhome > 3: self._intervals[devicename] = self._intervals[devicename] * 2 def update_device(self, devicename): """Update the device_tracker entity.""" from pyicloud.exceptions import PyiCloudNoDevicesException # An entity will not be created by see() when track=false in # 'known_devices.yaml', but we need to see() it at least once entity = self.hass.states.get(ENTITY_ID_FORMAT.format(devicename)) if entity is None and devicename in self.seen_devices: return attrs = {} kwargs = {} if self.api is None: return try: for device in self.api.devices: if str(device) != str(self.devices[devicename]): continue status = device.status(DEVICESTATUSSET) dev_id = status['name'].replace(' ', '', 99) dev_id = slugify(dev_id) attrs[ATTR_DEVICESTATUS] = DEVICESTATUSCODES.get( status['deviceStatus'], 'error') attrs[ATTR_LOWPOWERMODE] = status['lowPowerMode'] attrs[ATTR_BATTERYSTATUS] = status['batteryStatus'] attrs[ATTR_ACCOUNTNAME] = self.accountname status = device.status(DEVICESTATUSSET) battery = status.get('batteryLevel', 0) * 100 location = status['location'] if location: self.determine_interval( devicename, location['latitude'], location['longitude'], battery) interval = self._intervals.get(devicename, 1) attrs[ATTR_INTERVAL] = interval accuracy = location['horizontalAccuracy'] kwargs['dev_id'] = dev_id kwargs['host_name'] = status['name'] kwargs['gps'] = (location['latitude'], location['longitude']) kwargs['battery'] = battery kwargs['gps_accuracy'] = accuracy kwargs[ATTR_ATTRIBUTES] = attrs self.see(**kwargs) self.seen_devices[devicename] = True except PyiCloudNoDevicesException: _LOGGER.error("No iCloud Devices found") def lost_iphone(self, devicename): """Call the lost iPhone function if the device is found.""" if self.api is None: return self.api.authenticate() for device in self.api.devices: if devicename is None or device == self.devices[devicename]: device.play_sound() def update_icloud(self, devicename=None): """Authenticate against iCloud and scan for devices.""" from pyicloud.exceptions import PyiCloudNoDevicesException if self.api is None: return try: if devicename is not None: if devicename in self.devices: self.devices[devicename].location() else: _LOGGER.error("devicename %s unknown for account %s", devicename, self._attrs[ATTR_ACCOUNTNAME]) else: for device in self.devices: self.devices[device].location() except PyiCloudNoDevicesException: _LOGGER.error("No iCloud Devices found") def setinterval(self, interval=None, devicename=None): """Set the interval of the given devices.""" devs = [devicename] if devicename else self.devices for device in devs: devid = '{}.{}'.format(DOMAIN, device) devicestate = self.hass.states.get(devid) if interval is not None: if devicestate is not None: self._overridestates[device] = active_zone( self.hass, float(devicestate.attributes.get('latitude', 0)), float(devicestate.attributes.get('longitude', 0))) if self._overridestates[device] is None: self._overridestates[device] = 'away' self._intervals[device] = interval else: self._overridestates[device] = None self.update_device(device)
parser = argparse.ArgumentParser( description='iLocatorBridge - Bridge between iCloud location and OpenHAB') parser.add_argument( '-c', '--config', dest='config', default=DEFAULT_CONFIG, help='Config location (default: %s)' % (DEFAULT_CONFIG, )) parser.add_argument( '-v', '--verbose', dest='verbose', action='store_true', help='Be more verbose in the output') args = parser.parse_args() logging.basicConfig( filename=DEFAULT_LOGFILE, level=args.verbose and logging.DEBUG or logging.INFO, format='%(asctime)s %(message)s') gConfigurationiCloud, _, _ = configurationManager(args.config) api = PyiCloudService(gConfigurationiCloud['username'], gConfigurationiCloud['password']) if api.requires_2fa: print "Two-factor 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)
class Icloud(object): """Represent an icloud account in Home Assistant.""" def __init__(self, hass, username, password, name, see): """Initialize an iCloud account.""" self.hass = hass self.username = username self.password = password self.api = None self.accountname = name self.devices = {} self.seen_devices = {} self._overridestates = {} self._intervals = {} self.see = see self._trusted_device = None self._verification_code = None self._attrs = {} self._attrs[ATTR_ACCOUNTNAME] = name self.reset_account_icloud() randomseconds = random.randint(10, 59) track_utc_time_change(self.hass, self.keep_alive, second=randomseconds) def reset_account_icloud(self): """Reset an icloud account.""" from pyicloud import PyiCloudService from pyicloud.exceptions import PyiCloudFailedLoginException, PyiCloudNoDevicesException icloud_dir = self.hass.config.path("icloud") if not os.path.exists(icloud_dir): os.makedirs(icloud_dir) try: self.api = PyiCloudService(self.username, self.password, cookie_directory=icloud_dir, verify=True) except PyiCloudFailedLoginException as error: self.api = None _LOGGER.error("Error logging into iCloud Service: %s", error) return try: self.devices = {} self._overridestates = {} self._intervals = {} for device in self.api.devices: status = device.status(DEVICESTATUSSET) devicename = slugify(status["name"].replace(" ", "", 99)) if devicename not in self.devices: self.devices[devicename] = device self._intervals[devicename] = 1 self._overridestates[devicename] = None except PyiCloudNoDevicesException: _LOGGER.error("No iCloud Devices found!") def icloud_trusted_device_callback(self, callback_data): """The trusted device is chosen.""" self._trusted_device = int(callback_data.get("0", "0")) self._trusted_device = self.api.trusted_devices[self._trusted_device] if self.accountname in _CONFIGURING: request_id = _CONFIGURING.pop(self.accountname) configurator = get_component("configurator") configurator.request_done(request_id) def icloud_need_trusted_device(self): """We need a trusted device.""" configurator = get_component("configurator") if self.accountname in _CONFIGURING: return devicesstring = "" devices = self.api.trusted_devices for i, device in enumerate(devices): devicesstring += "{}: {};".format(i, device.get("deviceName")) _CONFIGURING[self.accountname] = configurator.request_config( self.hass, "iCloud {}".format(self.accountname), self.icloud_trusted_device_callback, description=("Please choose your trusted device by entering" " the index from this list: " + devicesstring), entity_picture="/static/images/config_icloud.png", submit_caption="Confirm", fields=[{"id": "0"}], ) def icloud_verification_callback(self, callback_data): """The trusted device is chosen.""" self._verification_code = callback_data.get("0") if self.accountname in _CONFIGURING: request_id = _CONFIGURING.pop(self.accountname) configurator = get_component("configurator") configurator.request_done(request_id) def icloud_need_verification_code(self): """We need a verification code.""" configurator = get_component("configurator") if self.accountname in _CONFIGURING: return if self.api.send_verification_code(self._trusted_device): self._verification_code = "waiting" _CONFIGURING[self.accountname] = configurator.request_config( self.hass, "iCloud {}".format(self.accountname), self.icloud_verification_callback, description=("Please enter the validation code:"), entity_picture="/static/images/config_icloud.png", submit_caption="Confirm", fields=[{"code": "0"}], ) def keep_alive(self, now): """Keep the api alive.""" from pyicloud.exceptions import PyiCloud2FARequiredError if self.api is None: self.reset_account_icloud() if self.api is None: return if self.api.requires_2fa: try: self.api.authenticate() except PyiCloud2FARequiredError: if self._trusted_device is None: self.icloud_need_trusted_device() return if self._verification_code is None: self.icloud_need_verification_code() return if self._verification_code == "waiting": return if self.api.validate_verification_code(self._trusted_device, self._verification_code): self._verification_code = None else: self.api.authenticate() currentminutes = dt_util.now().hour * 60 + dt_util.now().minute for devicename in self.devices: interval = self._intervals.get(devicename, 1) if (currentminutes % interval == 0) or (interval > 10 and currentminutes % interval in [2, 4]): self.update_device(devicename) def determine_interval(self, devicename, latitude, longitude, battery): """Calculate new interval.""" distancefromhome = None zone_state = self.hass.states.get("zone.home") zone_state_lat = zone_state.attributes["latitude"] zone_state_long = zone_state.attributes["longitude"] distancefromhome = distance(latitude, longitude, zone_state_lat, zone_state_long) distancefromhome = round(distancefromhome / 1000, 1) currentzone = active_zone(self.hass, latitude, longitude) if (currentzone is not None and currentzone == self._overridestates.get(devicename)) or ( currentzone is None and self._overridestates.get(devicename) == "away" ): return self._overridestates[devicename] = None if currentzone is not None: self._intervals[devicename] = 30 return if distancefromhome is None: return if distancefromhome > 25: self._intervals[devicename] = round(distancefromhome / 2, 0) elif distancefromhome > 10: self._intervals[devicename] = 5 else: self._intervals[devicename] = 1 if battery is not None and battery <= 33 and distancefromhome > 3: self._intervals[devicename] = self._intervals[devicename] * 2 def update_device(self, devicename): """Update the device_tracker entity.""" from pyicloud.exceptions import PyiCloudNoDevicesException # An entity will not be created by see() when track=false in # 'known_devices.yaml', but we need to see() it at least once entity = self.hass.states.get(ENTITY_ID_FORMAT.format(devicename)) if entity is None and devicename in self.seen_devices: return attrs = {} kwargs = {} if self.api is None: return try: for device in self.api.devices: if str(device) != str(self.devices[devicename]): continue status = device.status(DEVICESTATUSSET) dev_id = status["name"].replace(" ", "", 99) dev_id = slugify(dev_id) attrs[ATTR_DEVICESTATUS] = DEVICESTATUSCODES.get(status["deviceStatus"], "error") attrs[ATTR_LOWPOWERMODE] = status["lowPowerMode"] attrs[ATTR_BATTERYSTATUS] = status["batteryStatus"] attrs[ATTR_ACCOUNTNAME] = self.accountname status = device.status(DEVICESTATUSSET) battery = status.get("batteryLevel", 0) * 100 location = status["location"] if location: self.determine_interval(devicename, location["latitude"], location["longitude"], battery) interval = self._intervals.get(devicename, 1) attrs[ATTR_INTERVAL] = interval accuracy = location["horizontalAccuracy"] kwargs["dev_id"] = dev_id kwargs["host_name"] = status["name"] kwargs["gps"] = (location["latitude"], location["longitude"]) kwargs["battery"] = battery kwargs["gps_accuracy"] = accuracy kwargs[ATTR_ATTRIBUTES] = attrs self.see(**kwargs) self.seen_devices[devicename] = True except PyiCloudNoDevicesException: _LOGGER.error("No iCloud Devices found!") def lost_iphone(self, devicename): """Call the lost iphone function if the device is found.""" if self.api is None: return self.api.authenticate() for device in self.api.devices: if devicename is None or device == self.devices[devicename]: device.play_sound() def update_icloud(self, devicename=None): """Authenticate against iCloud and scan for devices.""" from pyicloud.exceptions import PyiCloudNoDevicesException if self.api is None: return try: if devicename is not None: if devicename in self.devices: self.devices[devicename].update_icloud() else: _LOGGER.error("devicename %s unknown for account %s", devicename, self._attrs[ATTR_ACCOUNTNAME]) else: for device in self.devices: self.devices[device].update_icloud() except PyiCloudNoDevicesException: _LOGGER.error("No iCloud Devices found!") def setinterval(self, interval=None, devicename=None): """Set the interval of the given devices.""" devs = [devicename] if devicename else self.devices for device in devs: devid = DOMAIN + "." + device devicestate = self.hass.states.get(devid) if interval is not None: if devicestate is not None: self._overridestates[device] = active_zone( self.hass, float(devicestate.attributes.get("latitude", 0)), float(devicestate.attributes.get("longitude", 0)), ) if self._overridestates[device] is None: self._overridestates[device] = "away" self._intervals[device] = interval else: self._overridestates[device] = None self.update_device(device)