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 news(news_source: str = 'fox') -> None: """Says news around the user's location. Args: news_source: Source from where the news has to be fetched. Defaults to ``fox``. """ if not env.news_api: logger.warning("News apikey not found.") support.no_env_vars() return sys.stdout.write(f'\rGetting news from {news_source} news.') news_client = NewsApiClient(api_key=env.news_api) try: all_articles = news_client.get_top_headlines( sources=f'{news_source}-news') except newsapi_exception.NewsAPIException: speaker.speak( text=f"I wasn't able to get the news {env.title}! " "I think the News API broke, you may try after sometime.") return speaker.speak(text="News around you!") speaker.speak(text=' '.join( [article['title'] for article in all_articles['articles']])) if shared.called_by_offline: return if shared.called['report'] or shared.called['time_travel']: speaker.speak(run=True)
def vehicle(operation: str, temp: int = None) -> Union[str, dict, None]: """Establishes a connection with the car and returns an object to control the primary vehicle. Args: operation: Operation to be performed. temp: Temperature for climate control. Returns: str: Returns the vehicle's name. """ try: connection = connector.Connect(username=env.car_email, password=env.car_pass) connection.connect() if not connection.head: return vehicles = connection.get_vehicles( headers=connection.head).get("vehicles") primary_vehicle = [ each_vehicle for each_vehicle in vehicles if each_vehicle.get("role") == "Primary" ][0] control = controller.Control(vin=primary_vehicle.get("vin"), connection=connection) response = {} if operation == "LOCK": response = control.lock(pin=env.car_pin) elif operation == "UNLOCK": response = control.unlock(pin=env.car_pin) elif operation == "START": lock_status = { each_dict['key']: each_dict['value'] for each_dict in [ key for key in control.get_status().get( 'vehicleStatus').get('coreStatus') if key.get('key') in ["DOOR_IS_ALL_DOORS_LOCKED", "DOOR_BOOT_LOCK_STATUS"] ] } if lock_status.get('DOOR_IS_ALL_DOORS_LOCKED', 'FALSE') != 'TRUE' or \ lock_status.get('DOOR_BOOT_LOCK_STATUS', 'UNLOCKED') != 'LOCKED': logger.warning("Car is unlocked when tried to remote start!") if lock_response := control.lock( pin=env.car_pin).get("failureDescription"): logger.error(lock_response) response = control.remote_engine_start(pin=env.car_pin, target_temperature=temp) elif operation == "STOP": response = control.remote_engine_stop(pin=env.car_pin)
def terminator() -> NoReturn: """Exits the process with specified status without calling cleanup handlers, flushing stdio buffers, etc. Using this, eliminates the hassle of forcing multiple threads to stop. """ pid = os.getpid() proc = psutil.Process(pid=pid) logger.info(f"Terminating process: {pid}") try: proc.wait(timeout=5) except psutil.TimeoutExpired: logger.warning(f"Failed to terminate process in 5 seconds: {pid}") if proc.is_running(): logger.info(f"{pid} is still running. Killing it.") proc.kill()
def robinhood() -> None: """Gets investment details from robinhood API.""" if not all([env.robinhood_user, env.robinhood_pass, env.robinhood_qr]): logger.warning("Robinhood username, password or QR code not found.") support.no_env_vars() return sys.stdout.write("\rGetting your investment details.") rh = Robinhood() rh.login(username=env.robinhood_user, password=env.robinhood_pass, qr_code=env.robinhood_qr) raw_result = rh.positions() result = raw_result["results"] stock_value = watcher(rh, result) speaker.speak(text=stock_value)
def kill_port_pid(port: int, protocol: str = 'tcp') -> Union[bool, None]: """Uses List all open files ``lsof`` to get the PID of the process that is listening on the given port and kills it. Args: port: Port number which the application is listening on. protocol: Protocol serving the port. Defaults to ``TCP`` Warnings: **Use only when the application listening to given port runs as a dedicated/child process with a different PID** - This function will kill the process that is using the given port. - If the PID is the same as that of ``MainProcess``, triggers a warning without terminating the process. Returns: bool: Flag to indicate whether the process was terminated successfully. """ try: active_sessions = subprocess.check_output( f"lsof -i {protocol}:{port}", shell=True).decode('utf-8').splitlines() for each in active_sessions: each_split = each.split() if each_split[0].strip() == 'Python': logger.info( f'Application hosted on {each_split[-2]} is listening to port: {port}' ) pid = int(each_split[1]) if pid == os.getpid(): called_function = sys._getframe(1).f_code.co_name # noqa called_file = sys._getframe(1).f_code.co_filename.replace( f'{os.getcwd()}/', '') # noqa logger.warning( f"{called_function} from {called_file} tried to kill the running process." ) warnings.warn( f'OPERATION DENIED: {called_function} from {called_file} tried to kill the running process.' ) return os.kill(pid, signal.SIGTERM) logger.info(f'Killed PID: {pid}') return True logger.info(f'No active process running on {port}') return False except (subprocess.SubprocessError, subprocess.CalledProcessError) as error: logger.error(error)
def github(phrase: str) -> None: """Pre-process to check the phrase received and call the ``GitHub`` function as necessary. Args: phrase: Takes the phrase spoken as an argument. """ if 'update yourself' in phrase or 'update your self' in phrase: update() return if not all([env.git_user, env.git_pass]): logger.warning("Github username or token not found.") support.no_env_vars() return auth = HTTPBasicAuth(env.git_user, env.git_pass) response = requests.get( 'https://api.github.com/user/repos?type=all&per_page=100', auth=auth).json() result, repos, total, forked, private, archived, licensed = [], [], 0, 0, 0, 0, 0 for i in range(len(response)): total += 1 forked += 1 if response[i]['fork'] else 0 private += 1 if response[i]['private'] else 0 archived += 1 if response[i]['archived'] else 0 licensed += 1 if response[i]['license'] else 0 repos.append({ response[i]['name'].replace('_', ' ').replace('-', ' '): response[i]['clone_url'] }) if 'how many' in phrase: speaker.speak( text= f'You have {total} repositories {env.title}, out of which {forked} are forked, {private} are private, ' f'{licensed} are licensed, and {archived} archived.') elif not shared.called_by_offline: [ result.append(clone_url) if clone_url not in result and re.search(rf'\b{word}\b', repo.lower()) else None for word in phrase.lower().split() for item in repos for repo, clone_url in item.items() ] if result: github_controller(target=result) else: speaker.speak(text=f"Sorry {env.title}! I did not find that repo.")
def events_gatherer() -> str: """Uses ``applescript`` to fetch events from local Calendar (including subscriptions) or Microsoft Outlook. See Also: When reading from apple ``Calendar``, the calendar should be named as ``Jarvis`` Returns: str: - On success, returns a message saying which event is scheduled at what time. - If no events, returns a message saying there are no events in the next 12 hours. - On failure, returns a message saying Jarvis was unable to read calendar/outlook. """ if not env.macos: return f"Reading events from {env.event_app} is not possible on Windows operating system." failure = None process = subprocess.Popen(["/usr/bin/osascript", fileio.event_script] + [str(arg) for arg in [1, 3]], stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = process.communicate() os.system(f"git checkout -- {fileio.event_script}" ) # Undo the unspecified changes done by ScriptEditor if error := process.returncode: # stores non zero error err_msg = err.decode("UTF-8") err_code = err_msg.split()[-1].strip() if err_code == "(-1728)": # If 'Jarvis' is unavailable in calendar/outlook application logger.warning(f"'Jarvis' is unavailable in {env.event_app}.") return f"Jarvis is unavailable in your {env.event_app} {env.title}!" elif err_code == "(-1712)": # If an event takes 2+ minutes, the Apple Event Manager reports a time-out error. failure = f"{env.event_app}/event took an unusually long time to respond/complete.\nInclude, " \ f"'with timeout of 300 seconds' to your {fileio.event_script} right after the " \ f"'tell application {env.event_app}' step and 'end timeout' before the 'end tell' step." elif err_code in ["(-10810)", "(-609)", "(-600)" ]: # If unable to launch the app or app terminates. event_app_launcher() if not failure: failure = f"Unable to read {env.event_app} - [{error}]\n{err_msg}" logger.error(failure) # failure = failure.replace('"', '') # An identifier can’t go after this “"” # os.system(f"""osascript -e 'display notification "{failure}" with title "Jarvis"'""") return f"I was unable to read your {env.event_app} {env.title}! Please make sure it is in sync."
def car(phrase: str) -> None: """Controls the car to lock, unlock or remote start. Args: phrase: Takes the phrase spoken as an argument. See Also: API climate controls: 31 is LO, 57 is HOT Car Climate controls: 58 is LO, 84 is HOT """ if not all([env.car_email, env.car_pass, env.car_pin]): logger.warning("InControl email or password or PIN not found.") support.no_env_vars() return disconnected = f"I wasn't able to connect your car {env.title}! Please check the logs for more information." if "start" in phrase or "set" in phrase or "turn on" in phrase: if not shared.called_by_offline: playsound(sound=indicators.exhaust, block=False) extras = "" if target_temp := support.extract_nos(input_=phrase, method=int): if target_temp < 57: target_temp = 57 elif target_temp > 83: target_temp = 83 elif "high" in phrase or "highest" in phrase: target_temp = 83 elif "low" in phrase or "lowest" in phrase: target_temp = 57 else: if vehicle_position := vehicle(operation="LOCATE_INTERNAL"): current_temp, target_temp = get_current_temp( location=vehicle_position) extras += f"Your car is in {vehicle_position['city']} {vehicle_position['state']}, where the " \ f"current temperature is {current_temp}, so " else:
def hostname_to_ip(hostname: str) -> list: """Uses ``socket.gethostbyname_ex`` to translate a host name to IPv4 address format, extended interface. See Also: - A host may have multiple interfaces. - | In case of true DNS being used or the host entry file is carefully handwritten, the system will look | there to find the translation. - | But depending on the configuration, the host name can be bound to all the available interfaces, including | the loopback ones. - ``gethostbyname`` returns the address of the first of those interfaces in its own order. - | To get the assigned IP, ``gethostbyname_ex`` is used, which returns a list of all the interfaces, including | the host spot connected, and loopback IPs. References: https://docs.python.org/3/library/socket.html#socket.gethostbyname_ex Args: hostname: Takes the hostname of a device as an argument. """ try: _hostname, _alias_list, _ipaddr_list = socket.gethostbyname_ex(hostname) except socket.error as error: logger.error(f"{error} on {hostname}") return [] logger.info({"Hostname": _hostname, "Alias": _alias_list, "Interfaces": _ipaddr_list}) if not _ipaddr_list: logger.critical(f"No interfaces found for {hostname}") elif len(_ipaddr_list) > 1: logger.warning(f"Host {hostname} has multiple interfaces. {_ipaddr_list}") return _ipaddr_list else: ip_addr = ip_address() if _ipaddr_list[0].split('.')[0] == ip_addr.split('.')[0]: return _ipaddr_list logger.error(f"NetworkID of the InterfaceIP of host {hostname} does not match the network id of DeviceIP.") return []
def write_current_location() -> NoReturn: """Extracts location information from public IP address and writes it to a yaml file.""" if os.path.isfile(fileio.location): try: with open(fileio.location) as file: data = yaml.load(stream=file, Loader=yaml.FullLoader) or {} except yaml.YAMLError as error: data = {} logger.error(error) address = data.get("address") if address and data.get("reserved") and data.get("latitude") and data.get("longitude") and \ address.get("city", address.get("hamlet")) and address.get("country") and \ address.get("state", address.get("county")): logger.info(f"{fileio.location} is reserved.") logger.warning("Automatic location detection has been disabled!") return current_lat, current_lon = get_coordinates_from_ip() location_info = get_location_from_coordinates(coordinates=(current_lat, current_lon)) current_tz = TimezoneFinder().timezone_at(lat=current_lat, lng=current_lon) logger.info(f"Writing location info in {fileio.location}") with open(fileio.location, 'w') as location_writer: yaml.dump(data={"timezone": current_tz, "latitude": current_lat, "longitude": current_lon, "address": location_info}, stream=location_writer, default_flow_style=False)
def system_vitals() -> None: """Reads system vitals on macOS. See Also: - Jarvis will suggest a reboot if the system uptime is more than 2 days. - If confirmed, invokes `restart <https://thevickypedia.github.io/Jarvis/#jarvis.restart>`__ function. """ output = "" if env.macos: if not env.root_password: speaker.speak( text= f"You haven't provided a root password for me to read system vitals {env.title}! " "Add the root password as an environment variable for me to read." ) return logger.info('Fetching system vitals') cpu_temp, gpu_temp, fan_speed, output = None, None, None, "" # Tested on 10.13, 10.14, 11.6 and 12.3 versions if not shared.hosted_device or not shared.hosted_device.get( 'os_version'): logger.warning( "hosted_device information was not loaded during startup. Reloading now." ) shared.hosted_device = hosted_device_info() if packaging.version.parse(shared.hosted_device.get( 'os_version')) > packaging.version.parse('10.14'): critical_info = [ each.strip() for each in (os.popen( f'echo {env.root_password} | sudo -S powermetrics --samplers smc -i1 -n1' )).read().split('\n') if each != '' ] support.flush_screen() for info in critical_info: if 'CPU die temperature' in info: cpu_temp = info.strip('CPU die temperature: ').replace( ' C', '').strip() if 'GPU die temperature' in info: gpu_temp = info.strip('GPU die temperature: ').replace( ' C', '').strip() if 'Fan' in info: fan_speed = info.strip('Fan: ').replace(' rpm', '').strip() else: fan_speed = subprocess.check_output( f'echo {env.root_password} | sudo -S spindump 1 1 -file /tmp/spindump.txt > /dev/null 2>&1;grep ' f'"Fan speed" /tmp/spindump.txt;sudo rm /tmp/spindump.txt', shell=True).decode('utf-8') if cpu_temp: cpu = f'Your current average CPU temperature is ' \ f'{support.format_nos(input_=temperature.c2f(arg=support.extract_nos(input_=cpu_temp)))}' \ f'\N{DEGREE SIGN}F. ' output += cpu speaker.speak(text=cpu) if gpu_temp: gpu = f'GPU temperature is {support.format_nos(temperature.c2f(support.extract_nos(gpu_temp)))}' \ f'\N{DEGREE SIGN}F. ' output += gpu speaker.speak(text=gpu) if fan_speed: fan = f'Current fan speed is {support.format_nos(support.extract_nos(fan_speed))} RPM. ' output += fan speaker.speak(text=fan) restart_time = datetime.fromtimestamp(psutil.boot_time()) second = (datetime.now() - restart_time).total_seconds() restart_time = datetime.strftime(restart_time, "%A, %B %d, at %I:%M %p") restart_duration = support.time_converter(seconds=second) output += f'Restarted on: {restart_time} - {restart_duration} ago from now.' if shared.called_by_offline: speaker.speak(text=output) return sys.stdout.write(f'\r{output}') speaker.speak( text= f"Your {shared.hosted_device.get('device')} was last booted on {restart_time}. " f"Current boot time is: {restart_duration}.") if second >= 259_200: # 3 days if boot_extreme := re.search('(.*) days', restart_duration): warn = int(boot_extreme.group().replace(' days', '').strip()) speaker.speak( text= f"{env.title}! your {shared.hosted_device.get('device')} has been running for more " f"than {warn} days. You must consider a reboot for better performance. Would you like " f"me to restart it for you {env.title}?", run=True) response = listener.listen(timeout=3, phrase_limit=3) if any(word in response.lower() for word in keywords.ok): logger.info( f'JARVIS::Restarting {shared.hosted_device.get("device")}') restart(ask=False)
def lights(phrase: str) -> Union[None, NoReturn]: """Controller for smart lights. Args: phrase: Takes the voice recognized statement as argument. """ if not vpn_checker(): return if not os.path.isfile(fileio.smart_devices): logger.warning(f"{fileio.smart_devices} not found.") support.no_env_vars() return try: with open(fileio.smart_devices) as file: smart_devices = yaml.load(stream=file, Loader=yaml.FullLoader) or {} except yaml.YAMLError as error: logger.error(error) speaker.speak( text= f"I'm sorry {env.title}! I wasn't able to read the source information. " "Please check the logs.") return if not any(smart_devices): logger.warning(f"{fileio.smart_devices} is empty.") support.no_env_vars() return phrase = phrase.lower() def turn_off(host: str) -> NoReturn: """Turns off the device. Args: host: Takes target device IP address as an argument. """ smart_lights.MagicHomeApi(device_ip=host, device_type=1, operation='Turn Off').turn_off() def warm(host: str) -> NoReturn: """Sets lights to warm/yellow. Args: host: Takes target device IP address as an argument. """ smart_lights.MagicHomeApi(device_ip=host, device_type=1, operation='Warm Lights').update_device( r=0, g=0, b=0, warm_white=255) def cool(host: str) -> NoReturn: """Sets lights to cool/white. Args: host: Takes target device IP address as an argument. """ smart_lights.MagicHomeApi(device_ip=host, device_type=2, operation='Cool Lights').update_device( r=255, g=255, b=255, warm_white=255, cool_white=255) def preset(host: str, value: int) -> NoReturn: """Changes light colors to preset values. Args: host: Takes target device IP address as an argument. value: Preset value extracted from list of verified values. """ smart_lights.MagicHomeApi( device_ip=host, device_type=2, operation='Preset Values').send_preset_function( preset_number=value, speed=101) def lumen(host: str, rgb: int = 255) -> NoReturn: """Sets lights to custom brightness. Args: host: Takes target device IP address as an argument. rgb: Red, Green andBlue values to alter the brightness. """ args = {'r': 255, 'g': 255, 'b': 255, 'warm_white': rgb} smart_lights.MagicHomeApi( device_ip=host, device_type=1, operation='Custom Brightness').update_device(**args) if 'all' in phrase.split(): # Checking for list since lights are inserted as a list and tv as string host_names = [ value for key, value in smart_devices.items() if isinstance(value, list) ] light_location = "" else: # Get the closest matching name provided in smart_devices.yaml compared to what's requested by the user light_location = support.get_closest_match(text=phrase, match_list=list( smart_devices.keys())) host_names = [smart_devices.get(light_location)] light_location = light_location.replace('_', ' ').replace('-', '') host_names = support.matrix_to_flat_list(input_=host_names) host_names = list(filter(None, host_names)) if light_location and not host_names: logger.warning( f"No hostname values found for {light_location} in {fileio.smart_devices}" ) speaker.speak( text= f"I'm sorry {env.title}! You haven't mentioned the host names of '{light_location}' lights." ) return if not host_names: Thread(target=support.unrecognized_dumper, args=[{ 'LIGHTS': phrase }]).start() speaker.speak(text=f"I'm not sure which lights you meant {env.title}!") return host_ip = [ support.hostname_to_ip(hostname=hostname) for hostname in host_names ] # host_ip = list(filter(None, host_ip)) host_ip = support.matrix_to_flat_list(input_=host_ip) if not host_ip: plural = 'lights' if len(host_ip) > 1 else 'light' speaker.speak( text= f"I wasn't able to connect to your {light_location} {plural} {env.title}! " f"{support.number_to_words(input_=len(host_ip), capitalize=True)} lights appear to be " "powered off.") return def avail_check(function_to_call: Callable) -> NoReturn: """Speaks an error message if any of the lights aren't reachable. Args: function_to_call: Takes the function/method that has to be called as an argument. """ status = ThreadPool(processes=1).apply_async(func=thread_worker, args=[function_to_call]) speaker.speak(run=True) if failed := status.get(timeout=5): plural_ = "lights aren't available right now!" if failed > 1 else "light isn't available right now!" speaker.speak( text= f"I'm sorry sir! {support.number_to_words(input_=failed, capitalize=True)} {plural_}" )
def television(phrase: str) -> None: """Controls all actions on a TV (LG Web OS). Args: phrase: Takes the voice recognized statement as argument. """ if not vpn_checker(): return if not os.path.isfile(fileio.smart_devices): logger.warning(f"{fileio.smart_devices} not found.") support.no_env_vars() return try: with open(fileio.smart_devices) as file: smart_devices = yaml.load(stream=file, Loader=yaml.FullLoader) or {} except yaml.YAMLError as error: logger.error(error) speaker.speak( text= f"I'm sorry {env.title}! I was unable to read your TV's source information." ) return if not env.tv_mac or not env.tv_client_key: logger.warning("IP, MacAddress [or] ClientKey not found.") support.no_env_vars() return tv_ip_list = support.hostname_to_ip( hostname=smart_devices.get('tv', 'LGWEBOSTV')) tv_ip_list = list(filter(None, tv_ip_list)) if not tv_ip_list: speaker.speak( text= f"I'm sorry {env.title}! I wasn't able to get the IP address of your TV." ) return phrase_exc = phrase.replace('TV', '') phrase_lower = phrase_exc.lower() def tv_status(attempt: int = 0) -> str: """Pings the tv and returns the status. 0 if able to ping, 256 if unable to ping. Args: attempt: Takes iteration count as an argument. Returns: int: Returns the reachable IP address from the list. """ for ip in tv_ip_list: if env.macos: if tv_stat := os.system( f"ping -c 1 -t 2 {ip} >/dev/null 2>&1"): logger.error( f"Connection timed out on {ip}. Ping result: {tv_stat}" ) if not attempt else None else: return ip else: if tv_stat := os.system(f"ping -c 1 -t 2 {ip} > NUL"): logger.error( f"Connection timed out on {ip}. Ping result: {tv_stat}" ) if not attempt else None else:
def weather(phrase: str = None) -> None: """Says weather at any location if a specific location is mentioned. Says weather at current location by getting IP using reverse geocoding if no place is received. Args: phrase: Takes the phrase spoken as an argument. """ if not env.weather_api: logger.warning("Weather apikey not found.") support.no_env_vars() return place = None if phrase: place = support.get_capitalized(phrase=phrase) phrase = phrase.lower() sys.stdout.write('\rGetting your weather info') if place: desired_location = geo_locator.geocode(place) coordinates = desired_location.latitude, desired_location.longitude located = geo_locator.reverse(coordinates, language='en') address = located.raw['address'] city = address.get('city') state = address.get('state') lat = located.latitude lon = located.longitude else: try: with open(fileio.location) as file: current_location = yaml.load(stream=file, Loader=yaml.FullLoader) except yaml.YAMLError as error: logger.error(error) speaker.speak( text= f"I'm sorry {env.title}! I wasn't able to read your location.") return address = current_location.get('address', {}) city = address.get('city', address.get('hamlet', 'Unknown')) state = address.get('state', 'Unknown') lat = current_location['latitude'] lon = current_location['longitude'] weather_url = f'https://api.openweathermap.org/data/2.5/onecall?lat={lat}&lon={lon}&exclude=minutely,' \ f'hourly&appid={env.weather_api}' try: response = json.loads(urllib.request.urlopen( url=weather_url).read()) # loads the response in a json except (urllib.error.HTTPError, urllib.error.URLError) as error: logger.error(error) speaker.speak( text= f"I'm sorry {env.title}! I ran into an exception. Please check your logs." ) return weather_location = f'{city} {state}'.replace( 'None', '') if city != state else city or state if phrase and any(match_word in phrase for match_word in [ 'tomorrow', 'day after', 'next week', 'tonight', 'afternoon', 'evening' ]): if 'tonight' in phrase: key = 0 tell = 'tonight' elif 'day after' in phrase: key = 2 tell = 'day after tomorrow ' elif 'tomorrow' in phrase: key = 1 tell = 'tomorrow ' elif 'next week' in phrase: key = -1 next_week = datetime.fromtimestamp( response['daily'][-1]['dt']).strftime("%A, %B %d") tell = f"on {' '.join(next_week.split()[0:-1])} {engine().ordinal(next_week.split()[-1])}" else: key = 0 tell = 'today ' if 'morning' in phrase: when = 'morn' tell += 'morning' elif 'evening' in phrase: when = 'eve' tell += 'evening' elif 'tonight' in phrase: when = 'night' elif 'night' in phrase: when = 'night' tell += 'night' else: when = 'day' tell += '' if 'alerts' in response: alerts = response['alerts'][0]['event'] start_alert = datetime.fromtimestamp( response['alerts'][0]['start']).strftime("%I:%M %p") end_alert = datetime.fromtimestamp( response['alerts'][0]['end']).strftime("%I:%M %p") else: alerts, start_alert, end_alert = None, None, None during_key = response['daily'][key] condition = during_key['weather'][0]['description'] high = int(temperature.k2f(during_key['temp']['max'])) low = int(temperature.k2f(during_key['temp']['min'])) temp_f = int(temperature.k2f(during_key['temp'][when])) temp_feel_f = int(temperature.k2f(during_key['feels_like'][when])) sunrise = datetime.fromtimestamp( during_key['sunrise']).strftime("%I:%M %p") sunset = datetime.fromtimestamp( during_key['sunset']).strftime("%I:%M %p") if 'sunrise' in phrase or 'sun rise' in phrase or ('sun' in phrase and 'rise' in phrase): if datetime.strptime(datetime.today().strftime("%I:%M %p"), "%I:%M %p") >= \ datetime.strptime(sunrise, "%I:%M %p"): tense = "will be" else: tense = "was" speaker.speak( text= f"{tell} in {weather_location}, sunrise {tense} at {sunrise}.") return if 'sunset' in phrase or 'sun set' in phrase or ('sun' in phrase and 'set' in phrase): if datetime.strptime(datetime.today().strftime("%I:%M %p"), "%I:%M %p") >= \ datetime.strptime(sunset, "%I:%M %p"): tense = "will be" else: tense = "was" speaker.speak( text=f"{tell} in {weather_location}, sunset {tense} at {sunset}" ) return output = f"The weather in {weather_location} {tell} would be {temp_f}\N{DEGREE SIGN}F, with a high of " \ f"{high}, and a low of {low}. " if temp_feel_f != temp_f: output += f"But due to {condition} it will fee like it is {temp_feel_f}\N{DEGREE SIGN}F. " output += f"Sunrise at {sunrise}. Sunset at {sunset}. " if alerts and start_alert and end_alert: output += f'There is a weather alert for {alerts} between {start_alert} and {end_alert}' speaker.speak(text=output) return condition = response['current']['weather'][0]['description'] high = int(temperature.k2f(arg=response['daily'][0]['temp']['max'])) low = int(temperature.k2f(arg=response['daily'][0]['temp']['min'])) temp_f = int(temperature.k2f(arg=response['current']['temp'])) temp_feel_f = int(temperature.k2f(arg=response['current']['feels_like'])) sunrise = datetime.fromtimestamp( response['daily'][0]['sunrise']).strftime("%I:%M %p") sunset = datetime.fromtimestamp( response['daily'][0]['sunset']).strftime("%I:%M %p") if phrase: if 'sunrise' in phrase or 'sun rise' in phrase or ('sun' in phrase and 'rise' in phrase): if datetime.strptime(datetime.today().strftime("%I:%M %p"), "%I:%M %p") >= \ datetime.strptime(sunrise, "%I:%M %p"): tense = "will be" else: tense = "was" speaker.speak( text=f"In {weather_location}, sunrise {tense} at {sunrise}.") return if 'sunset' in phrase or 'sun set' in phrase or ('sun' in phrase and 'set' in phrase): if datetime.strptime(datetime.today().strftime("%I:%M %p"), "%I:%M %p") >= \ datetime.strptime(sunset, "%I:%M %p"): tense = "will be" else: tense = "was" speaker.speak( text=f"In {weather_location}, sunset {tense} at {sunset}") return if shared.called['time_travel']: if 'rain' in condition or 'showers' in condition: feeling = 'rainy' weather_suggest = 'You might need an umbrella" if you plan to head out.' elif temp_feel_f <= 40: feeling = 'freezing' weather_suggest = 'Perhaps" it is time for winter clothing.' elif 41 <= temp_feel_f <= 60: feeling = 'cool' weather_suggest = 'I think a lighter jacket would suffice" if you plan to head out.' elif 61 <= temp_feel_f <= 75: feeling = 'optimal' weather_suggest = 'It might be a perfect weather for a hike, or perhaps a walk.' elif 76 <= temp_feel_f <= 85: feeling = 'warm' weather_suggest = 'It is a perfect weather for some outdoor entertainment.' elif temp_feel_f > 85: feeling = 'hot' weather_suggest = "I would not recommend thick clothes today." else: feeling, weather_suggest = '', '' wind_speed = response['current']['wind_speed'] if wind_speed > 10: output = f'The weather in {city} is {feeling} {temp_f}\N{DEGREE SIGN}, but due to the current wind ' \ f'conditions (which is {wind_speed} miles per hour), it feels like {temp_feel_f}\N{DEGREE SIGN}.' \ f' {weather_suggest}' else: output = f'The weather in {city} is {feeling} {temp_f}\N{DEGREE SIGN}, and it currently feels like ' \ f'{temp_feel_f}\N{DEGREE SIGN}. {weather_suggest}' else: output = f'The weather in {weather_location} is {temp_f}\N{DEGREE SIGN}F, with a high of {high}, and a low ' \ f'of {low}. It currently feels Like {temp_feel_f}\N{DEGREE SIGN}F, and the current condition is ' \ f'{condition}. Sunrise at {sunrise}. Sunset at {sunset}.' if 'alerts' in response: alerts = response['alerts'][0]['event'] start_alert = datetime.fromtimestamp( response['alerts'][0]['start']).strftime("%I:%M %p") end_alert = datetime.fromtimestamp( response['alerts'][0]['end']).strftime("%I:%M %p") else: alerts, start_alert, end_alert = None, None, None if alerts and start_alert and end_alert: output += f' You have a weather alert for {alerts} between {start_alert} and {end_alert}' speaker.speak(text=output)