Ejemplo n.º 1
0
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]
Ejemplo n.º 2
0
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)
Ejemplo n.º 3
0
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)
Ejemplo n.º 4
0
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()
Ejemplo n.º 5
0
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)
Ejemplo n.º 6
0
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)
Ejemplo n.º 7
0
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.")
Ejemplo n.º 8
0
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."
Ejemplo n.º 9
0
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:
Ejemplo n.º 10
0
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 []
Ejemplo n.º 11
0
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)
Ejemplo n.º 12
0
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)
Ejemplo n.º 13
0
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_}"
            )
Ejemplo n.º 14
0
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:
Ejemplo n.º 15
0
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)