Example #1
0
    def __init__(self, input_device_index: int = None):
        """Initiates Porcupine object for hot word detection.

        Args:
            input_device_index: Index of Input Device to use.

        See Also:
            - Instantiates an instance of Porcupine object and monitors audio stream for occurrences of keywords.
            - A higher sensitivity results in fewer misses at the cost of increasing the false alarm rate.
            - sensitivity: Tolerance/Sensitivity level. Takes argument or env var ``sensitivity`` or defaults to ``0.5``

        References:
            - `Audio Overflow <https://people.csail.mit.edu/hubert/pyaudio/docs/#pyaudio.Stream.read>`__ handling.
        """
        logger.info(f"Initiating hot-word detector with sensitivity: {env.sensitivity}")
        keyword_paths = [pvporcupine.KEYWORD_PATHS[x] for x in [pathlib.PurePath(__file__).stem]]
        self.input_device_index = input_device_index
        self.recorded_frames = []

        self.py_audio = PyAudio()
        self.detector = pvporcupine.create(
            library_path=pvporcupine.LIBRARY_PATH,
            model_path=pvporcupine.MODEL_PATH,
            keyword_paths=keyword_paths,
            sensitivities=[env.sensitivity]
        )
        self.audio_stream = None
Example #2
0
def speak(text: str = None, run: bool = False, block: bool = True) -> NoReturn:
    """Calls ``audio_driver.say`` to speak a statement from the received text.

    Args:
        text: Takes the text that has to be spoken as an argument.
        run: Takes a boolean flag to choose whether to run the ``audio_driver.say`` loop.
        block: Takes a boolean flag to wait other tasks while speaking. [Applies only for larynx running on docker]
    """
    caller = sys._getframe(1).f_code.co_name  # noqa
    if text:
        text = text.replace('\n', '\t').strip()
        shared.text_spoken = text
        if shared.called_by_offline:
            shared.offline_caller = caller
            return
        logger.info(f'Speaker called by: {caller}')
        logger.info(f'Response: {text}')
        sys.stdout.write(f"\r{text}")
        if env.speech_synthesis_timeout and \
                speech_synthesizer(text=text) and \
                os.path.isfile(fileio.speech_synthesis_wav):
            playsound(sound=fileio.speech_synthesis_wav, block=block)
            os.remove(fileio.speech_synthesis_wav)
        else:
            audio_driver.say(text=text)
    if run:
        audio_driver.runAndWait()
    Thread(target=frequently_used, kwargs={
        "function_name": caller
    }).start() if caller in FUNCTIONS_TO_TRACK else None
Example #3
0
def trigger_api() -> None:
    """Initiates the fast API in a dedicated process using uvicorn server.

    See Also:
        - Checks if the port is being used. If so, makes a ``GET`` request to the endpoint.
        - Attempts to kill the process listening to the port, if the endpoint doesn't respond.
    """
    url = f'http://{env.offline_host}:{env.offline_port}'

    if is_port_in_use(port=env.offline_port):
        logger.info(f'{env.offline_port} is currently in use.')

        try:
            res = requests.get(url=url, timeout=1)
            if res.ok:
                logger.info(f'{url} is accessible.')
                return
            raise requests.exceptions.ConnectionError
        except (requests.exceptions.ConnectionError, requests.exceptions.Timeout):
            logger.error('Unable to connect to existing uvicorn server.')

        if not kill_port_pid(port=env.offline_port):  # This might terminate Jarvis
            logger.critical('Failed to kill existing PID. Attempting to re-create session.')

    argument_dict = {
        "app": "api.fast:app",
        "host": env.offline_host,
        "port": env.offline_port,
        "reload": True
    }
    if not env.macos:
        del argument_dict['reload']

    config = uvicorn.Config(**argument_dict)
    APIServer(config=config).run_in_parallel()
Example #4
0
def threat_notify(converted: str, date_extn: Union[str, None], gmail_user: str, gmail_pass: str,
                  phone_number: str, recipient: str) -> NoReturn:
    """Sends an SMS and email notification in case of a threat.

    References:
        Uses `gmail-connector <https://pypi.org/project/gmail-connector/>`__ to send the SMS and email.

    Args:
        converted: Takes the voice recognized statement as argument.
        date_extn: Name of the attachment file which is the picture of the intruder.
        gmail_user: Email address for the gmail account.
        gmail_pass: Password of the gmail account.
        phone_number: Phone number to send SMS.
        recipient: Email address of the recipient.
    """
    if converted:
        communicator.notify(user=gmail_user, password=gmail_pass, number=phone_number, subject="!!INTRUDER ALERT!!",
                            body=f"{datetime.now().strftime('%B %d, %Y %I:%M %p')}\n{converted}")
        body_ = f"""<html><head></head><body><h2>Conversation of Intruder:</h2><br>{converted}<br><br>
                                    <h2>Attached is a photo of the intruder.</h2>"""
    else:
        communicator.notify(user=gmail_user, password=gmail_pass, number=phone_number, subject="!!INTRUDER ALERT!!",
                            body=f"{datetime.now().strftime('%B %d, %Y %I:%M %p')}\n"
                                 "Check your email for more information.")
        body_ = """<html><head></head><body><h2>No conversation was recorded,
                                but attached is a photo of the intruder.</h2>"""
    if date_extn:
        attachment_ = f'threat/{date_extn}.jpg'
        response_ = SendEmail(gmail_user=gmail_user, gmail_pass=gmail_pass,
                              recipient=recipient, body=body_, attachment=attachment_,
                              subject=f"Intruder Alert on {datetime.now().strftime('%B %d, %Y %I:%M %p')}").send_email()
        if response_.ok:
            logger.info('Email has been sent!')
        else:
            logger.error(f"Email dispatch failed with response: {response_.body}\n")
Example #5
0
def sentry_mode() -> NoReturn:
    """Listens forever and invokes ``initiator()`` when recognized. Stops when ``restart`` table has an entry.

    See Also:
        - Gets invoked only when run from Mac-OS older than 10.14.
        - A regular listener is used to convert audio to text.
        - The text is then condition matched for wake-up words.
        - Additional wake words can be passed in a list as an env var ``LEGACY_KEYWORDS``.
    """
    try:
        while True:
            sys.stdout.write("\rSentry Mode")
            if wake_word := listener.listen(timeout=10, phrase_limit=2.5, sound=False, stdout=False):
                support.flush_screen()
                if any(word in wake_word.lower() for word in env.legacy_keywords):
                    playsound(sound=indicators.acknowledgement, block=False)
                    if phrase := listener.listen(timeout=env.timeout, phrase_limit=env.phrase_limit, sound=False):
                        initiator(phrase=phrase, should_return=True)
                        speaker.speak(run=True)
            if flag := support.check_restart():
                logger.info(f"Restart condition is set to {flag[0]} by {flag[1]}")
                if flag[1] == "OFFLINE":
                    stop_processes()
                    for _handler in logger.handlers:
                        if isinstance(_handler, logging.FileHandler):
                            logger.removeHandler(hdlr=_handler)
                    handler = custom_handler()
                    logger.info(f"Switching to {handler.baseFilename}")
                    logger.addHandler(hdlr=handler)
                    shared.processes = start_processes()
                else:
                    stop_processes(func_name=flag[1])
                    shared.processes[flag[1]] = start_processes(flag[1])
Example #6
0
def check_existing() -> bool:
    """Checks for existing connection.

    Returns:
        bool:
        A boolean flag whether a valid connection is present.
    """
    if is_port_in_use(port=env.speech_synthesis_port):
        logger.info(f'{env.speech_synthesis_port} is currently in use.')
        try:
            res = requests.get(
                url=
                f"http://{env.speech_synthesis_host}:{env.speech_synthesis_port}",
                timeout=1)
            if res.ok:
                logger.info(
                    f'http://{env.speech_synthesis_host}:{env.speech_synthesis_port} is accessible.'
                )
                return True
            return False
        except (ConnectionError, TimeoutError, requests.exceptions.RequestException, requests.exceptions.Timeout) as \
                error:
            logger.error(error)
            if not kill_port_pid(port=env.speech_synthesis_port):
                logger.critical(
                    'Failed to kill existing PID. Attempting to re-create session.'
                )
Example #7
0
def start_processes(
        func_name: str = None) -> Union[Process, Dict[str, Process]]:
    """Initiates multiple background processes to achieve parallelization.

    Methods
        - poll_for_messages: Initiates polling for messages on the telegram bot.
        - trigger_api: Initiates Jarvis API using uvicorn server to receive offline commands.
        - automator: Initiates automator that executes offline commands and certain functions at said time.
        - initiate_tunneling: Initiates ngrok tunnel to host Jarvis API through a public endpoint.
        - write_current_location: Writes current location details into a yaml file.
        - speech_synthesis: Initiates larynx docker image.
        - playsound: Plays a start-up sound.
    """
    processes = {
        "handler": Process(target=handler),
        "trigger_api": Process(target=trigger_api),
        "automator": Process(target=automator),
        "initiate_tunneling": Process(target=initiate_tunneling),
        "write_current_location": Process(target=write_current_location),
        "synthesizer": Process(target=docker_container.synthesizer)
    }
    if func_name:
        processes = {func_name: processes[func_name]}
    for func, process in processes.items():
        process.start()
        logger.info(
            f"Started function: {func} {process.sentinel} with PID: {process.pid}"
        )
    playsound(sound=indicators.initialize,
              block=False) if not func_name else None
    return processes[func_name] if func_name else processes
Example #8
0
def listen(timeout: Union[int, float], phrase_limit: Union[int, float], sound: bool = True,
           stdout: bool = True) -> Union[str, None]:
    """Function to activate listener, this function will be called by most upcoming functions to listen to user input.

    Args:
        timeout: Time in seconds for the overall listener to be active.
        phrase_limit: Time in seconds for the listener to actively listen to a sound.
        sound: Flag whether to play the listener indicator sound. Defaults to True unless set to False.
        stdout: Flag whether to print the listener status on screen.

    Returns:
        str:
         - Returns recognized statement from the microphone.
    """
    with Microphone() as source:
        try:
            playsound(sound=indicators.start, block=False) if sound else None
            sys.stdout.write("\rListener activated...") if stdout else None
            listened = recognizer.listen(source=source, timeout=timeout, phrase_time_limit=phrase_limit)
            playsound(sound=indicators.end, block=False) if sound else None
            support.flush_screen()
            recognized = recognizer.recognize_google(audio_data=listened)
            logger.info(recognized)
            return recognized
        except (UnknownValueError, RequestError, WaitTimeoutError):
            return
        except (ConnectionError, TimeoutError, requests.exceptions.RequestException, requests.exceptions.Timeout) as \
                error:
            logger.error(error)
Example #9
0
        def wrapper(*args: Any, **kwargs: Any) -> Callable:
            """Executes the wrapped function in a loop for the number of attempts mentioned.

            Args:
                *args: Arguments.
                **kwargs: Keyword arguments.

            Returns:
                Callable:
                Return value of the function implemented.

            Raises:
                Raises the exception as received beyond the given number of attempts.
            """
            return_exc = None
            for i in range(1, attempts + 1):
                try:
                    return_val = func(*args, **kwargs)
                    # Log messages only when the function did not return during the first attempt
                    logger.info(msg=f"{func.__name__} returned at {engine().ordinal(num=i)} attempt") if i > 1 else None
                    return return_val
                except exclude_exc or KeyboardInterrupt as excl_error:
                    logger.error(msg=excl_error)
                except Exception as error:
                    return_exc = error
                time.sleep(interval)
            logger.error(msg=f"{func.__name__} exceeded retry count::{attempts}")
            if return_exc and warn:
                warnings.warn(
                    f"{type(return_exc).__name__}: {return_exc}"
                )
            elif return_exc:
                raise return_exc
Example #10
0
def text_to_audio(
        text: str,
        filename: Union[FilePath, str] = None) -> Union[FilePath, str]:
    """Converts text into an audio file.

    Args:
        filename: Name of the file that has to be generated.
        text: Text that has to be converted to audio.
    """
    if not filename:
        if shared.offline_caller:
            filename = f"{shared.offline_caller}.wav"
            shared.offline_caller = None  # Reset caller after using it
        else:
            filename = f"{int(time.time())}.wav"
    process = Process(target=_generate_audio_file,
                      kwargs={
                          'filename': filename,
                          'text': text
                      })
    process.start()
    while True:
        if os.path.isfile(filename) and os.stat(filename).st_size:
            time.sleep(0.5)
            break
    logger.info(f"Generated {filename}")
    data, samplerate = soundfile.read(file=filename)
    soundfile.write(file=filename, data=data, samplerate=samplerate)
    return filename
Example #11
0
def create_reminder(hour,
                    minute,
                    am_pm,
                    message,
                    to_about,
                    timer: str = None) -> NoReturn:
    """Creates the lock file necessary to set a reminder.

    Args:
        hour: Hour of reminder time.
        minute: Minute of reminder time.
        am_pm: AM/PM of reminder time.
        message: Message to be reminded for.
        to_about: remind to or remind about as said in phrase.
        timer: Number of minutes/hours to reminder.
    """
    if not os.path.isdir('reminder'):
        os.mkdir('reminder')
    pathlib.Path(
        os.path.join(
            "reminder",
            f"{hour}_{minute}_{am_pm}|{message.replace(' ', '_')}.lock")
    ).touch()
    if timer:
        logger.info(
            f"Reminder created for '{message}' at {hour}:{minute} {am_pm}")
        speaker.speak(
            text=f"{random.choice(conversation.acknowledgement)}! "
            f"I will remind you {to_about} {message}, after {timer}.")
    else:
        speaker.speak(
            text=f"{random.choice(conversation.acknowledgement)}! "
            f"I will remind you {to_about} {message}, at {hour}:{minute} {am_pm}."
        )
Example #12
0
def create_alarm(hour: str, minute: str, am_pm: str, phrase: str, timer: str = None) -> NoReturn:
    """Creates the lock file necessary to set an alarm/timer.

    Args:
        hour: Hour of alarm time.
        minute: Minute of alarm time.
        am_pm: AM/PM of alarm time.
        phrase: Phrase spoken.
        timer: Number of minutes/hours to alarm.
    """
    if not os.path.isdir('alarm'):
        os.mkdir('alarm')
    pathlib.Path(f'alarm/{hour}_{minute}_{am_pm}.lock').touch()
    if 'wake' in phrase:
        speaker.speak(text=f"{random.choice(conversation.acknowledgement)}! "
                           f"I will wake you up at {hour}:{minute} {am_pm}.")
    elif 'timer' in phrase and timer:
        logger.info(f"Timer set at {hour}:{minute} {am_pm}")
        response = [f"{random.choice(conversation.acknowledgement)}! I have set a timer for {timer}.",
                    f"{timer}! Counting down.."]
        speaker.speak(text=random.choice(response))
    else:
        if datetime.strptime(datetime.today().strftime("%I:%M %p"), "%I:%M %p") >= \
                datetime.strptime(f"{hour}:{minute} {am_pm}", "%I:%M %p"):
            add = " tomorrow."
        else:
            add = "."
        response = [f"Alarm has been set for {hour}:{minute} {am_pm}{add}",
                    f"Your alarm is set for {hour}:{minute} {am_pm}{add}"]
        speaker.speak(text=f"{random.choice(conversation.acknowledgement)}! {random.choice(response)}")
Example #13
0
def delete_db() -> NoReturn:
    """Delete base db if exists. Called upon restart or shut down."""
    if os.path.isfile(fileio.base_db):
        logger.info(f"Removing {fileio.base_db}")
        os.remove(fileio.base_db)
    if os.path.isfile(fileio.base_db):
        raise FileExistsError(f"{fileio.base_db} still exists!")
    return
Example #14
0
def automator() -> NoReturn:
    """Place for long-running background tasks.

    See Also:
        - The automation file should be a dictionary within a dictionary that looks like the below:

            .. code-block:: yaml

                6:00 AM:
                  task: set my bedroom lights to 50%
                9:00 PM:
                  task: set my bedroom lights to 5%

        - Jarvis creates/swaps a ``status`` flag upon execution, so that it doesn't repeat execution within a minute.
    """
    offline_list = compatibles.offline_compatible() + keywords.restart_control
    start_events = start_meetings = time.time()
    events.event_app_launcher()
    dry_run = True
    while True:
        if os.path.isfile(fileio.automation):
            if exec_task := auto_helper(offline_list=offline_list):
                offline_communicator(command=exec_task)

        if start_events + env.sync_events <= time.time() or dry_run:
            start_events = time.time()
            event_process = Process(target=events.events_writer)
            logger.info(f"Getting calendar events from {env.event_app}") if dry_run else None
            event_process.start()
            with db.connection:
                cursor = db.connection.cursor()
                cursor.execute("INSERT INTO children (events) VALUES (?);", (event_process.pid,))
                db.connection.commit()

        if start_meetings + env.sync_meetings <= time.time() or dry_run:
            if dry_run and env.ics_url:
                try:
                    if requests.get(url=env.ics_url).status_code == 503:
                        env.sync_meetings = 21_600  # Set to 6 hours if unable to connect to the meetings URL
                except (ConnectionError, TimeoutError, requests.exceptions.RequestException,
                        requests.exceptions.Timeout) as error:
                    logger.error(error)
                    env.sync_meetings = 99_999_999  # NEVER RUN, since env vars are loaded only once during start up
            start_meetings = time.time()
            meeting_process = Process(target=icalendar.meetings_writer)
            logger.info("Getting calendar schedule from ICS.") if dry_run else None
            meeting_process.start()
            with db.connection:
                cursor = db.connection.cursor()
                cursor.execute("INSERT INTO children (meetings) VALUES (?);", (meeting_process.pid,))
                db.connection.commit()

        if alarm_state := support.lock_files(alarm_files=True):
            for each_alarm in alarm_state:
                if each_alarm == datetime.now().strftime("%I_%M_%p.lock"):
                    Process(target=alarm_executor).start()
                    os.remove(os.path.join("alarm", each_alarm))
Example #15
0
def _generate_audio_file(filename: Union[FilePath, str],
                         text: str) -> NoReturn:
    """Generates an audio file from text.

    Args:
        filename: Filename to be generated.
        text: Text that has to be converted into audio.
    """
    logger.info(f"Generating audio into {filename} from the text: {text}")
    audio_driver.save_to_file(filename=filename, text=text)
    audio_driver.runAndWait()
Example #16
0
def stop_processes(func_name: str = None) -> NoReturn:
    """Stops all background processes initiated during startup and removes database source file."""
    stop_child_processes() if func_name in ["automator"] else None
    for func, process in shared.processes.items():
        if func_name and func_name != func:
            continue
        if process.is_alive():
            logger.info(f"Sending [SIGTERM] to {func} with PID: {process.pid}")
            process.terminate()
        if process.is_alive():
            logger.info(f"Sending [SIGKILL] to {func} with PID: {process.pid}")
            process.kill()
Example #17
0
def delay_condition(phrase: str, delay: Union[int, float]) -> None:
    """Delays the execution after sleeping for the said time, after which it is sent to ``offline_communicator``.

    Args:
        phrase: Takes the phrase spoken as an argument.
        delay: Sleeps for the number of seconds.
    """
    logger.info(
        f"'{phrase}' will be executed after {support.time_converter(seconds=delay)}"
    )
    time.sleep(delay)
    logger.info(f"Executing '{phrase}'")
    offline_communicator(command=phrase)
Example #18
0
def restart_control(phrase: str = None, quiet: bool = False) -> NoReturn:
    """Controls the restart functions based on the user request.

    Args:
        phrase: Takes the phrase spoken as an argument.
        quiet: Take a boolean flag to restart without warning.
    """
    if phrase and ('pc' in phrase.lower() or 'computer' in phrase.lower()
                   or 'machine' in phrase.lower()):
        logger.info(
            f'JARVIS::Restart for {shared.hosted_device.get("device")} has been requested.'
        )
        restart()
    else:
        caller = sys._getframe(1).f_code.co_name  # noqa
        logger.info(f'Called by {caller}')
        if quiet:  # restarted due internal errors
            logger.info(f"Restarting {caller}")
        elif shared.called_by_offline:  # restarted via automator
            logger.info("Restarting all background processes!")
            caller = "OFFLINE"
        else:
            speaker.speak(
                text=
                "I didn't quite get that. Did you mean restart your computer?")
            return
        with db.connection:
            cursor = db.connection.cursor()
            cursor.execute("INSERT INTO restart (flag, caller) VALUES (?,?);",
                           (True, caller))
            cursor.connection.commit()
Example #19
0
def initiate_tunneling() -> NoReturn:
    """Initiates Ngrok to tunnel requests from external sources if they aren't running already.

    Notes:
        - ``forever_ngrok.py`` is a simple script that triggers ngrok connection in the given offline port.
        - The connection is tunneled through a public facing URL used to make ``POST`` requests to Jarvis API.
    """
    if not env.macos:
        return
    pid_check = subprocess.check_output("ps -ef | grep forever_ngrok.py", shell=True)
    pid_list = pid_check.decode('utf-8').split('\n')
    for id_ in pid_list:
        if id_ and 'grep' not in id_ and '/bin/sh' not in id_:
            logger.info('An instance of ngrok tunnel for offline communicator is running already.')
            return
    if os.path.exists(f"{env.home}/JarvisHelper/venv/bin/activate"):
        logger.info('Initiating ngrok connection for offline communicator.')
        initiate = f'cd {env.home}/JarvisHelper && ' \
                   f'source venv/bin/activate && export host={env.offline_host} ' \
                   f'export port={env.offline_port} && python forever_ngrok.py'
        os.system(f"""osascript -e 'tell application "Terminal" to do script "{initiate}"' > /dev/null""")
    else:
        logger.info(f'JarvisHelper is not available to trigger an ngrok tunneling through {env.offline_port}')
        endpoint = rf'http:\\{env.offline_host}:{env.offline_port}'
        logger.info('However offline communicator can still be accessed via '
                    f'{endpoint}\\offline-communicator for API calls and {endpoint}\\docs for docs.')
Example #20
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()
Example #21
0
def save_audio(frames: list, sample_rate: int) -> NoReturn:
    """Converts audio frames into a recording to store it in a wav file.

    Args:
        frames: List of frames.
        sample_rate: Sample rate.
    """
    recordings_location = os.path.join(os.getcwd(), 'recordings')
    if not os.path.isdir(recordings_location):
        os.makedirs(recordings_location)
    filename = os.path.join(recordings_location, f"{datetime.now().strftime('%B_%d_%Y_%H%M')}.wav")
    logger.info(f"Saving {len(frames)} audio frames into {filename}")
    sys.stdout.write(f"\rSaving {len(frames)} audio frames into {filename}")
    recorded_audio = numpy.concatenate(frames, axis=0).astype(dtype=numpy.int16)
    soundfile.write(file=filename, data=recorded_audio, samplerate=sample_rate, subtype='PCM_16')
    support.flush_screen()
Example #22
0
 def start(self) -> NoReturn:
     """Runs ``audio_stream`` in a forever loop and calls ``initiator`` when the phrase ``Jarvis`` is heard."""
     try:
         while True:
             sys.stdout.write("\rSentry Mode")
             if not self.audio_stream:
                 self.open_stream()
             pcm = struct.unpack_from("h" * self.detector.frame_length,
                                      self.audio_stream.read(num_frames=self.detector.frame_length,
                                                             exception_on_overflow=False))
             self.recorded_frames.append(pcm)
             if self.detector.process(pcm=pcm) >= 0:
                 playsound(sound=indicators.acknowledgement, block=False)
                 self.close_stream()
                 if phrase := listener.listen(timeout=env.timeout, phrase_limit=env.phrase_limit, sound=False):
                     initiator(phrase=phrase, should_return=True)
                     speaker.speak(run=True)
             if flag := support.check_restart():
                 logger.info(f"Restart condition is set to {flag[0]} by {flag[1]}")
                 if flag[1] == "OFFLINE":
                     stop_processes()
                     for _handler in logger.handlers:
                         if isinstance(_handler, logging.FileHandler):
                             logger.removeHandler(hdlr=_handler)
                     handler = custom_handler()
                     logger.info(f"Switching to {handler.baseFilename}")
                     logger.addHandler(hdlr=handler)
                     shared.processes = start_processes()
                 else:
                     stop_processes(func_name=flag[1])
                     shared.processes[flag[1]] = start_processes(flag[1])
             if flag := support.check_stop():
                 logger.info(f"Stopper condition is set to {flag[0]} by {flag[1]}")
                 self.stop()
                 terminator()
Example #23
0
def stop_child_processes() -> NoReturn:
    """Stops sub processes (for meetings and events) triggered by child processes."""
    with db.connection:
        cursor_ = db.connection.cursor()
        children = cursor_.execute(
            "SELECT meetings, events FROM children").fetchone()
    for pid in children:
        if not pid:
            continue
        try:
            proc = psutil.Process(pid)
        except psutil.NoSuchProcess as error:
            logger.error(
                error
            )  # Occurs commonly since child processes run only for a short time
            continue
        if proc.is_running():
            logger.info(f"Sending [SIGTERM] to child process with PID: {pid}")
            proc.terminate()
        if proc.is_running():
            logger.info(f"Sending [SIGKILL] to child process with PID: {pid}")
            proc.kill()
Example #24
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)
Example #25
0
def speech_synthesizer(
        text: str,
        timeout: Union[int, float] = env.speech_synthesis_timeout,
        quality: str = "high",
        voice: str = "en-us_northern_english_male-glow_tts") -> bool:
    """Makes a post call to docker container for speech synthesis.

    Args:
        text: Takes the text that has to be spoken as an argument.
        timeout: Time to wait for the docker image to process text-to-speech request.
        quality: Quality at which the conversion is to be done.
        voice: Voice for speech synthesis.

    Returns:
        bool:
        A boolean flag to indicate whether speech synthesis has worked.
    """
    logger.info(f"Request for speech synthesis: {text}")
    if time_in_str := re.findall(r'(\d+:\d+\s?(?:AM|PM|am|pm:?))', text):
        for t_12 in time_in_str:
            t_24 = datetime.strftime(datetime.strptime(t_12, "%I:%M %p"),
                                     "%H:%M")
            logger.info(f"Converted {t_12} -> {t_24}")
            text = text.replace(t_12, t_24)
Example #26
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 []
Example #27
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)
Example #28
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)
Example #29
0
def conditions(converted: str, should_return: bool = False) -> bool:
    """Conditions function is used to check the message processed.

    Uses the keywords to match pre-defined conditions and trigger the appropriate function which has dedicated task.

    Args:
        converted: Takes the voice recognized statement as argument.
        should_return: A boolean flag sent by ``Activator`` to indicate that the ``else`` part shouldn't be executed.

    Returns:
        bool:
        Boolean True only when asked to sleep for conditioned sleep message.
    """
    converted_lower = converted.lower()
    todo_checks = ['to do', 'to-do', 'todo']

    if any(word in converted_lower for word in keywords.lights):
        lights(converted)

    elif any(word in converted_lower for word in keywords.television):
        television(converted)

    elif any(word in converted_lower for word in keywords.volume):
        volume(converted)

    elif any(word in converted_lower for word in keywords.car):
        car(converted_lower)

    elif any(word in converted_lower for word in keywords.weather):
        weather(converted)

    # ORDER OF THE ABOVE SHOULD BE RETAINED

    elif any(word in converted_lower for word in keywords.meetings):
        meetings()

    elif any(word in converted_lower for word in keywords.current_date) and \
            not any(word in converted_lower for word in keywords.avoid):
        current_date()

    elif any(word in converted_lower for word in keywords.current_time) and \
            not any(word in converted_lower for word in keywords.avoid):
        current_time(converted)

    elif any(word in converted_lower for word in keywords.system_info):
        system_info()

    elif any(word in converted_lower
             for word in keywords.ip_info) or 'IP' in converted.split():
        ip_info(converted)

    elif any(word in converted_lower for word in keywords.wikipedia_):
        wikipedia_()

    elif any(word in converted_lower for word in keywords.news):
        news()

    elif any(word in converted_lower for word in keywords.report):
        report()

    elif any(word in converted_lower for word in keywords.robinhood):
        robinhood()

    elif any(word in converted_lower for word in keywords.repeat):
        repeat()

    elif any(word in converted_lower for word in keywords.location):
        location()

    elif any(word in converted_lower for word in keywords.locate):
        locate(converted)

    elif any(word in converted_lower for word in keywords.read_gmail):
        read_gmail()

    elif any(word in converted_lower for word in keywords.meaning):
        meaning(converted)

    elif any(word in converted_lower for word in keywords.delete_todo) and 'items' in converted_lower and \
            any(word in converted_lower for word in todo_checks):
        delete_todo_items()

    elif any(word in converted_lower for word in keywords.todo):
        todo()

    elif any(word in converted_lower for word in keywords.add_todo) and \
            any(word in converted_lower for word in todo_checks):
        add_todo()

    elif any(word in converted_lower for word in keywords.delete_todo) and \
            any(word in converted_lower for word in todo_checks):
        delete_todo()

    elif any(word in converted_lower for word in keywords.distance) and \
            not any(word in converted_lower for word in keywords.avoid):
        distance(converted)

    elif any(word in converted_lower for word in conversation.form):
        speak(text="I am a program, I'm without form.")

    elif any(word in converted_lower for word in keywords.locate_places):
        locate_places(converted)

    elif any(word in converted_lower for word in keywords.directions):
        directions(converted)

    elif any(word in converted_lower for word in keywords.kill_alarm):
        kill_alarm(converted)

    elif any(word in converted_lower for word in keywords.set_alarm):
        set_alarm(converted)

    elif any(word in converted_lower for word in keywords.google_home):
        google_home()

    elif any(word in converted_lower for word in keywords.jokes):
        jokes()

    elif any(word in converted_lower for word in keywords.reminder):
        reminder(converted)

    elif any(word in converted_lower for word in keywords.notes):
        notes()

    elif any(word in converted_lower for word in keywords.github):
        github(converted)

    elif any(word in converted_lower for word in keywords.send_sms):
        send_sms(converted)

    elif any(word in converted_lower for word in keywords.google_search):
        google_search(converted)

    elif any(word in converted_lower for word in keywords.apps):
        apps(converted)

    elif any(word in converted_lower for word in keywords.music):
        music(converted)

    elif any(word in converted_lower for word in keywords.face_detection):
        face_detection()

    elif any(word in converted_lower for word in keywords.speed_test) and \
            ('internet' in converted_lower or 'connection' in converted_lower or 'run' in converted_lower):
        speed_test()

    elif any(word in converted_lower for word in keywords.bluetooth):
        bluetooth(converted)

    elif any(word in converted_lower for word in keywords.brightness):
        brightness(converted)

    elif any(word in converted_lower for word in keywords.guard_enable):
        guard_enable()

    elif any(word in converted_lower for word in keywords.flip_a_coin):
        flip_a_coin()

    elif any(word in converted_lower for word in keywords.facts):
        facts()

    elif any(word in converted_lower for word in keywords.events):
        events()

    elif any(word in converted_lower for word in keywords.voice_changer):
        voice_changer(converted)

    elif any(word in converted_lower for word in keywords.system_vitals):
        system_vitals()

    elif any(word in converted_lower for word in keywords.vpn_server):
        vpn_server(converted)

    elif any(word in converted_lower for word in keywords.automation):
        automation_handler(converted_lower)

    elif any(word in converted_lower for word in keywords.sprint):
        sprint_name()

    elif any(word in converted_lower for word in conversation.greeting):
        response = [
            'I am spectacular. I hope you are doing fine too.',
            'I am doing well. Thank you.', 'I am great. Thank you.'
        ]
        speak(text=random.choice(response))

    elif any(word in converted_lower for word in conversation.capabilities):
        speak(
            text=
            'There is a lot I can do. For example: I can get you the weather at any location, news around '
            'you, meanings of words, launch applications, create a to-do list, check your emails, get your '
            'system configuration, tell your investment details, locate your phone, find distance between '
            'places, set an alarm, play music on smart devices around you, control your TV, tell a joke, send'
            ' a message, set reminders, scan and clone your GitHub repositories, and much more. Time to ask,.'
        )

    elif any(word in converted_lower for word in conversation.languages):
        speak(
            text=
            "Tricky question!. I'm configured in python, and I can speak English."
        )

    elif any(word in converted_lower for word in conversation.whats_up):
        speak(
            text=
            "My listeners are up. There is nothing I cannot process. So ask me anything.."
        )

    elif any(word in converted_lower for word in conversation.what):
        speak(text="I'm just a pre-programmed virtual assistant.")

    elif any(word in converted_lower for word in conversation.who):
        speak(text="I am Jarvis. A virtual assistant designed by Mr.Raauv.")

    elif any(word in converted_lower for word in conversation.about_me):
        speak(
            text="I am Jarvis. A virtual assistant designed by Mr.Raauv. "
            "I'm just a pre-programmed virtual assistant, trying to become a natural language UI. "
            "I can seamlessly take care of your daily tasks, and also help with most of your work!"
        )

    elif any(word in converted_lower for word in keywords.sleep_control):
        return controls.sleep_control(converted)

    elif any(word in converted_lower for word in keywords.restart_control):
        controls.restart_control(converted)

    elif any(word in converted_lower for word in keywords.kill) and \
            not any(word in converted_lower for word in keywords.avoid):
        raise StopSignal

    elif any(word in converted_lower for word in keywords.shutdown):
        controls.shutdown()

    elif should_return:
        Thread(target=support.unrecognized_dumper,
               args=[{
                   'ACTIVATOR': converted
               }]).start()
        return False

    else:
        logger.info(f'Received unrecognized lookup parameter: {converted}')
        Thread(target=support.unrecognized_dumper,
               args=[{
                   'CONDITIONS': converted
               }]).start()
        if not alpha(text=converted):
            if not google_maps(query=converted):
                if google(query=converted):
                    # if none of the conditions above are met, opens a Google search on default browser
                    search_query = str(converted).replace(' ', '+')
                    unknown_url = f"https://www.google.com/search?q={search_query}"
                    webbrowser.open(url=unknown_url)
Example #30
0
        raise StopSignal
    else:
        speaker.speak(text=f"Machine state is left intact {env.title}!")
        return


def exit_process() -> NoReturn:
    """Function that holds the list of operations done upon exit."""
    reminders = {}
    alarms = support.lock_files(alarm_files=True)
    if reminder_files := support.lock_files(reminder_files=True):
        for file in reminder_files:
            split_val = file.replace('.lock', '').split('|')
            reminders.update({split_val[0]: split_val[-1]})
    if reminders:
        logger.info(f'JARVIS::Deleting Reminders - {reminders}')
        if len(reminders) == 1:
            speaker.speak(text=f'You have a pending reminder {env.title}!')
        else:
            speaker.speak(
                text=f'You have {len(reminders)} pending reminders {env.title}!'
            )
        for key, value in reminders.items():
            speaker.speak(
                text=
                f"{value.replace('_', ' ')} at {key.lstrip('0').replace('00', '').replace('_', ' ')}"
            )
    if alarms:
        alarms = ', and '.join(alarms) if len(alarms) != 1 else ''.join(alarms)
        alarms = alarms.replace('.lock', '').replace('_', ':').replace(
            ':PM', ' PM').replace(':AM', ' AM')