Artículo original: Python Project – How to Build Tony Stark's JARVIS with Python por Ashutosh Krishna

Traducido por: Jose González

¿Recuerdas a J.A.R.V.I.S., el asistente personal virtual de Tony Stark?. Si has visto alguna de las películas de Ironman o Los Vengadores, entonces estoy seguro de que lo recuerdas.

¿Alguna vez te preguntaste si podrías crear tu propio asistente personal?, ¿sí?, pues, ¡Tony Stark puede ayudarnos con eso!.

tony-snap2_rv5gmh

Oops, ¿acaso olvidaste que ya no está?. Es triste que ya no sea capaz de salvarnos. Pero, tu lenguaje de programación favorito, Python, puede ayudarte.

Si, leíste bien. Podemos crear nuestro propio J.A.R.V.I.S., usando Python. ¡Vamos a ello!

Preparación del Proyecto

Mientras estés escribiendo el código de este proyecto, te vas a encontrar con varios módulos y librerías externas. Aprendamos acerca de ellas e instalémoslas. Pero antes de instalarlas, creemos un entorno virtual y activémoslo.

Vamos a crear un entorno virtual usando virtualenv. Python ahora viene con una librería virtualenv preinstalada. Así que, para crear nuestro entorno virtual puedes usar el comando de abajo:

$ python -m venv env

El comando anterior va a crear un entorno virtual llamado env. Ahora, necesitamos activar el entorno usando el siguiente comando:

$ . env/Scripts/activate

Para verificar si es que el entorno ha sido activado o no, puedes ver (env)en tu terminal. Ahora, podemos instalar las librerías.

1. pyttsx3:

pyttsx es una librería multi-plataforma de texto a voz que es independiente a la plataforma donde se ejecute. La mayor ventaja de usar esta librería de conversión texto-a-voz es que trabaja sin conexión. Para instalar este modulo, escriba el siguiente comando en la terminal:

$ pip install pyttsx3

2. SpeechRecognition:

Esto nos permite convertir audio em texto para su procesamiento. Para instalar este module, escribe el siguiente comando en la terminal:

$ pip install SpeechRecognition

3. pywhatkit:

Esta es una librería de fácil uso que nos ayudará a interactuar con el navegador de forma más sencilla. Para instalar este módulo, ejecuta el siguiente comando en la terminal:

$ pip install pywhatkit

4. wikipedia:

Vamos a usarla para buscar una gran variedad de información del sitio web de Wikipedia. Para instalar este módulo, escribe el comando de a continuación en la terminal:

$ pip install wikipedia

5. requests:

Esta es una simple y elegante librería HTTP para Python que te permite enviar peticiones en HTTP/1.1 de manera muy sencilla. Para instalar este módulo, ejecuta el siguiente comando en la terminal:

$ pip install requests

Archivo .env

Necesitamos este archivo para poder alojar datos privados como claves API, contraseñas, así como todo lo que relacionado con el proyecto. Por ahora, vamos a almacenar el nombre del usuario y del bot.

Crea un archivo con el nombre .env y agrega la siguiente información allí:

USER=Usuario
BOTNAME=JARVIS

Para usar esta información del archivo .env, debemos instalar otro módulo llamado python-decouple así:

$ pip install python-decouple

Aprenda más acerca de los Entornos Variables en Python aquí.

Como configurar JARVIS con Python

Antes de que comencemos a definir algunas importantes funciones. Creemos primero un motor de voz.

import pyttsx3
from decouple import config

USERNAME = config('USER')
BOTNAME = config('BOTNAME')


engine = pyttsx3.init('sapi5')

# Set Rate
engine.setProperty('rate', 190)

# Set Volume
engine.setProperty('volume', 1.0)

# Set Voice (Female)
voices = engine.getProperty('voices')
engine.setProperty('voice', voices[1].id)

Analicemos el código anterior. Primero que todo, hemos inicializado un engine usando el módulo pyttsx3. sapi5 es una API de voz de Microsoft que nos ayuda a usar las voces. Aprende más acerca de eso aquí.

A continuación, vamos a configurar las propiedades de rate y volume del motor de voz usando setProperty.

Luego, podemos obtener las voces del motor usando getProperty. voices será una lista de voces disponibles en nuestro sistema. Si le visualizamos, podremos ver algo como lo siguiente:

[<pyttsx3.voice.Voice object at 0x000001AB9FB834F0>, <pyttsx3.voice.Voice object at 0x000001AB9FB83490>]

La primera corresponde a una voz masculina y la siguiente es una voz femenina. JARVIS solía ser un asistente masculino en las películas, pero he elegido fijar la propiedad voice a la femenina para este tutorial utilizando setProperty.

Nota: Si aparece un error relacionado a Pyaudio, descarga el archivo wheel Pyaudio.whl desde aquí e instálelo dentro del entorno virtual.

Además, usando config desde decouple, estamos obteniendo el valor de USER y BOTNAME de las variables del entorno.

Habilitar la función Hablar

La función Hablar será la responsable de enunciar cualquier texto que pase por ella. Veamos el código:

# Conversión Texto a Voz
def speak(text):
    """Usado para decir cualquier texto que le sea entregado"""
    
    engine.say(text)
    engine.runAndWait()

En speak(), el motor dice oralmente cualquier texto que es entregado mediante el uso de say(). El uso de runAndWait(),  se bloquea durante el bucle de eventos y vuelve cuando se borra la cola de comandos.

Habilitar la función Dar la bienvenida

Esta función será usada para dar la bienvenida al usuario cada momento que el programa se ejecuta. En concordancia con el horario en tiempo real, saludará con Buenos Días, Buenas Tardes, o Buenas Noches al usuario.

from datetime import datetime


def greet_user():
    """Saluda al usuario de acuerdo al horario"""
    
    hour = datetime.now().hour
    if (hour >= 6) and (hour < 12):
        speak(f"Buenos días {USERNAME}")
    elif (hour >= 12) and (hour < 16):
        speak(f"Buenas tardes {USERNAME}")
    elif (hour >= 16) and (hour < 19):
        speak(f"Buenas noches {USERNAME}")
    speak(f"Yo soy {BOTNAME}. ¿Cómo puedo asistirle?")

En primer lugar, consideramos la hora actual, en la situación que la hora actual sea las 11:15 AM, la hora será las 11. Si el valor de la hora está entre 6 AM y 12 PM, deseará los Buenos Días al usuario. Si el valor está entre 12 PM y 4 PM, le deseará las Buenas Tardes al usuario y de la misma manera, si el valor está entre las 4 PM y las 7 PM, le deseará las Buenas Noches. Estamos ocupando speak() para hablarle al usuario.

Como obtener informarción del usuario

Usamos esta función para poder tomar instrucciones o comandos por parte del usuario y también, poder reconocer el comando usando el módulo speech_recognition.

import speech_recognition as sr
from random import choice
from utils import opening_text


def take_user_input():
    """Toma las entradas del usuario, las reconoce utilizando el módulo de reconocimiento de voz y lo transforma a texto"""

    r = sr.Recognizer()
    with sr.Microphone() as source:
        print('Escuchando....')
        r.pause_threshold = 1
        audio = r.listen(source)

    try:
        print('Reconociendo...')
        query = r.recognize_google(audio, language='es-es')
        if not 'Salir' in query or 'Alto' in query:
            speak(choice(opening_text))
        else:
            hour = datetime.now().hour
            if hour >= 21 and hour < 6:
                speak("Buenas noches señor, !cuídese!")
            else:
                speak('¡Tenga un buen día señor!')
            exit()
    except Exception:
        speak('Disculpe, no he podido entender. ¿Podría decirlo de nuevo?')
        query = 'None'
    return query

Hemos importado el módulo speech_recognition como sr. La clase Recognizer dentro del módulo speech_recognition nos ayuda a reconocer el audio. El mismo módulo también tiene una clase Microphone que nos da acceso al micrófono del dispositivo. Así, con el micrófono como source, escuchamos el audio mediante el uso de listen() en la clase Recognizer.

Además, hemos fijado el valor de pause_threshold a 1, para que no compile, aunque hagamos una pausa de 1 segundo mientras hablamos.

Luego, usando recognize_google() desde la clase Recognizer, tratamso de reconocer el audio. El recognize_google() ejecuta un reconocimiento de voz en el audio que entregado, usando la API de reconocimiento de voz de Google.

Hemos fijado el lenguaje a es-es, el cual es el español que se habla en la España. Va a dar como resultado la transcripción del audio que no es más que una cadena. La almacenamos en una variable llamada query.

Si la variable query tiene las palabras salir o alto en ella, significa que le estamos pidiendo al asistente que se detenga inmediatamente. Así que, antes de detenerse, saludamos al usuario según la hora correspondiente. Si la hora está entre 21 y 6, le deseamos las Buenas Noches al usuario, además de algún otro mensaje.

Creamos el archivo utils.py el cual es solo una lista con algunas oraciones como:

opening_text = [
    "Genial, estoy en ello señor.",
    "Okay señor, trabajaré en ello.",
    "Deme un segundo señor.",
]

Si la variable query no tiene ninguna de estas dos palabras (exit or stop), entonces se dice algo para que sepa que le hemos escuchado. Para lograr eso, usaremos el comando choice() del módulo random para aleatoriamente seleccionar alguna de las oraciones que están en la lista de opening_text. Luego de hablar, salimos del programa.

Durante todo el proceso, si encontramos una excepción, nos disculpamos con ekl usuario y fijamos el valor de query a None. Al final, retornamos el valor de query.

Como configurar las funciones sin conexión

Dentro de la carpeta functions, crear una archivo Python llamado os_ops.py. En este archivo, crearemos varias funciones para interactuar con el sistema operativo.

import os
import subprocess as sp

paths = {
    'notepad': "C:\\Program Files\\Notepad++\\notepad++.exe",
    'discord': "C:\\Users\\ashut\\AppData\\Local\\Discord\\app-1.0.9003\\Discord.exe",
    'calculator': "C:\\Windows\\System32\\calc.exe"
}

En código anterior, hemos creado un diccionario llamado paths el cual tiene el nombre de un software como clave y su dirección como valor. Puedes modificar las direcciones de acuerdo a tu propio sistema y agregar más si lo estimas necesario.

Como abrir la camara

Vamos a usar la siguiente función para abrir la cámara de nuestro sistema. Usaremos el módulo subprocess para ejecutar esta instrucción.

def open_camera():
    sp.run('start microsoft.windows.camera:', shell=True)

Como abrir Notepadd++ y Discord

Usaremos las siguientes funciones para abrir Notepad++ y Discord en el sistema.

def open_notepad():
    os.startfile(paths['notepad'])


def open_discord():
    os.startfile(paths['discord'])

Como abrir la consola de comandos

Usaremos la siguiente función para abrir la consola de comandos de nuestro sistema.

def open_cmd():
    os.system('start cmd')

Como abrir la calculadora

Usaremos la función de a continuación para abrir la calculadora de nuestro sistema.

def open_calculator():
    sp.Popen(paths['calculator'])

Como configurar las funciones con conexión

Agregaremos varias funciones en línea. Estas son:

  1. Encuentra mi dirección IP
  2. Busca en Wikipedia
  3. Reproduce videos en YouTube
  4. Busca en Google
  5. Envía un mensaje por Whatsapp
  6. Envía un Correo electrónico
  7. Obtén los últimos titulares de las noticias
  8. Obtén el reporte del clima
  9. Obtén las películas en tendencia
  10. Obtener chistes aleatoriamente
  11. Obtener algún consejo al azar

Creemos un archivo llamado online_ops.py dentro del directorio functions, y comencemos a crear estas funciones una tras la otra. Por ahora, agrega el siguiente código en el archivo:

import requests
import wikipedia
import pywhatkit as kit
from email.message import EmailMessage
import smtplib
from decouple import config

Antes de empezar a trabajar con las API, si no has trabajado con las API y no sabes como manipularlas utilizando Python, revisa este tutorial.

Como agregar la función Encuentra mi dirección IP

ipify entrega una API bastante sencilla para las direcciones públicas IP. Solo necesitamos realizar una solicitud GET en la siguiente URL: https://api64.ipify.org/?format=json. Devolverá la JSON data como:

{
  "ip": "117.214.111.199"
}

Entonces, podríamos simplemente mostrar la ip desde la JSON data. Así que, creemos dicho comando:

def find_my_ip():
    ip_address = requests.get('https://api64.ipify.org?format=json').json()
    return ip_address["ip"]

Como agregar la función de Búsqueda en Wikipedia

Para buscar en Wikipedia, usaremos el módulo wikipedia que hemos instalado previamente en este tutorial.

def search_on_wikipedia(query):
    results = wikipedia.summary(query, sentences=2)
    return results

Dentro del módulo wikipedia, tenemos un summary() que acepta una variable query como elemento. Adicionalmente, podemos indicar el número de oraciones requeridas. Y, entonces, simplemente mostrar el resultado.

Como agregar la función para Reproducir videos en YouTube

Para reproducir videos en YouTube, usaremos PyWhatKit. El cual ya hemos importado mediante kit.

def play_on_youtube(video):
    kit.playonyt(video)

PyWhatKit tiene un playonyt() que acepta un tema como elemento. Entonces busca dicho tema en YouTube y reproduce el video más apropiado. Usa PyAutoGUI de manera encubierta.

Como agregar la función de búsqueda en google

Nuevamente, usaremos PyWhatKit para la búsqueda en google.

def search_on_google(query):
    kit.search(query)

Tiene un comando search() que nos ayuda a buscar en Google instantáneamente.

Como agregar la función Enviar un mensaje vía WhatsApp

Estaremos usando PyWhatKit nuevamente para enviar mensajes por whatsapp.

def send_whatsapp_message(number, message):
    kit.sendwhatmsg_instantly(f"+91{number}", message)

Nuestro comando acepta dos elementos  – el número de teléfono number y el message. Entonces llama a sendwhatmsg_instantly() para enviar un mensaje WhatsApp. Asegúrate de estar con la sesión de WhatsApp iniciada en WhatsApp Web.

Como agregar la función de enviar email

Para enviar emails, estaremos usando el módulo preinstalado smtplib de Python.

EMAIL = config("EMAIL")
PASSWORD = config("PASSWORD")


def send_email(receiver_address, subject, message):
    try:
        email = EmailMessage()
        email['To'] = receiver_address
        email["Subject"] = subject
        email['From'] = EMAIL
        email.set_content(message)
        s = smtplib.SMTP("smtp.gmail.com", 587)
        s.starttls()
        s.login(EMAIL, PASSWORD)
        s.send_message(email)
        s.close()
        return True
    except Exception as e:
        print(e)
        return False

El comando acepta receiver_address, subject, y message como elementos. Creamos un objeto de clase SMTP del módulo smtplib. Toma un host y un port number como parámetros.

Entonces iniciamos la sesión e ingresamos con la dirección de correo electrónico y la contraseña, para enviar el correo electrónico. Asegúrate de agregar tu Correo electrónico y tu contraseña en el archivo .env.

Como agregar la función para obtener los últimos titulares de las noticias

Para buscar los titulares de las últimas noticias, usaremos la NewsAPI. Nos registramos con una cuenta gratuita en NewsAPI y obtenemos nuestra clave API. Agregamos nuestra NEWS_API_KEY el archivo .env.

NEWS_API_KEY = config("NEWS_API_KEY")


def get_latest_news():
    news_headlines = []
    res = requests.get(
        f"https://newsapi.org/v2/top-headlines?country=in&apiKey={NEWS_API_KEY}&category=general").json()
    articles = res["articles"]
    for article in articles:
        news_headlines.append(article["title"])
    return news_headlines[:5]

En el comando anterior, primero estamos creando una lista vacía llamada news_headlines. Entonces haremos una solicitud GET en la API URL especificada en la Documentacion NewsAPI. Una muestra de una respuesta JSON de la solicitud luce como lo siguiente:

{
  "status": "ok",
  "totalResults": 38,
  "articles": [
    {
      "source": {
        "id": null,
        "name": "Sportskeeda"
      },
      "author": "Aniket Thakkar",
      "title": "Latest Free Fire redeem code to get Weapon loot crate today (14 October 2021) - Sportskeeda",
      "description": "Gun crates are one of the ways that players in Free Fire can obtain impressive and appealing gun skins.",
      "url": "https://www.sportskeeda.com/free-fire/latest-free-fire-redeem-code-get-weapon-loot-crate-today-14-october-2021",
      "urlToImage": "https://staticg.sportskeeda.com/editor/2021/10/d0b83-16341799119781-1920.jpg",
      "publishedAt": "2021-10-14T03:51:50Z",
      "content": null
    },
    {
      "source": {
        "id": null,
        "name": "NDTV News"
      },
      "author": null,
      "title": "BSF Gets Increased Powers In 3 Border States: What It Means - NDTV",
      "description": "Border Security Force (BSF) officers will now have the power toarrest, search, and of seizure to the extent of 50 km inside three newstates sharing international boundaries with Pakistan and Bangladesh.",
      "url": "https://www.ndtv.com/india-news/bsf-gets-increased-powers-in-3-border-states-what-it-means-2574644",
      "urlToImage": "https://c.ndtvimg.com/2021-08/eglno7qk_-bsf-recruitment-2021_625x300_10_August_21.jpg",
      "publishedAt": "2021-10-14T03:44:00Z",
      "content": "This move is quickly snowballing into a debate on state autonomy. New Delhi: Border Security Force (BSF) officers will now have the power to arrest, search, and of seizure to the extent of 50 km ins… [+4143 chars]"
    },
    {
      "source": {
        "id": "the-times-of-india",
        "name": "The Times of India"
      },
      "author": "TIMESOFINDIA.COM",
      "title": "5 health conditions that can make your joints hurt - Times of India",
      "description": "Joint pain caused by these everyday issues generally goes away on its own when you stretch yourself a little and flex your muscles.",
      "url": "https://timesofindia.indiatimes.com/life-style/health-fitness/health-news/5-health-conditions-that-can-make-your-joints-hurt/photostory/86994969.cms",
      "urlToImage": "https://static.toiimg.com/photo/86995017.cms",
      "publishedAt": "2021-10-14T03:30:00Z",
      "content": "Depression is a mental health condition, but the symptoms may manifest even on your physical health. Unexpected aches and pain in the joints that you may experience when suffering from chronic depres… [+373 chars]"
    },
    {
      "source": {
        "id": null,
        "name": "The Indian Express"
      },
      "author": "Devendra Pandey",
      "title": "Rahul Dravid likely to be interim coach for New Zealand series - The Indian Express",
      "description": "It’s learnt that a few Australian coaches expressed interest in the job, but the BCCI isn’t keen as they are focussing on an Indian for the role, before they look elsewhere.",
      "url": "https://indianexpress.com/article/sports/cricket/rahul-dravid-likely-to-be-interim-coach-for-new-zealand-series-7570990/",
      "urlToImage": "https://images.indianexpress.com/2021/05/rahul-dravid.jpg",
      "publishedAt": "2021-10-14T03:26:09Z",
      "content": "Rahul Dravid is likely to be approached by the Indian cricket board to be the interim coach for Indias home series against New Zealand. Head coach Ravi Shastri and the core of the support staff will … [+1972 chars]"
    },
    {
      "source": {
        "id": null,
        "name": "CNBCTV18"
      },
      "author": null,
      "title": "Thursday's top brokerage calls: Infosys, Wipro and more - CNBCTV18",
      "description": "Goldman Sachs has maintained its 'sell' rating on Mindtree largely due to expensive valuations, while UBS expects a muted reaction from Wipro's stock. Here are the top brokerage calls for the day:",
      "url": "https://www.cnbctv18.com/market/stocks/thursdays-top-brokerage-calls-infosys-wipro-and-more-11101072.htm",
      "urlToImage": "https://images.cnbctv18.com/wp-content/uploads/2019/03/buy-sell.jpg",
      "publishedAt": "2021-10-14T03:26:03Z",
      "content": "MiniGoldman Sachs has maintained its 'sell' rating on Mindtree largely due to expensive valuations, while UBS expects a muted reaction from Wipro's stock. Here are the top brokerage calls for the day:"
    }
  ]
}

Ya que las noticias están contenidas en una lista llamadaarticles, crearemos una variable articles con el valor res['articles']. Ahora, iteraremos sobre la lista articles anexando cada article["title"] con la lista news_headlines. Entonces mostraremos los primeros cinco últimos titulares de las noticias de esta lista.

Como agregar la función obtener el reporte del clima

Para obtener el reporte del clima, usaremos la OpenWeatherMap API. Nos registramos con una cuenta gratuita y obtenemos la APP ID. Asegúrate de agregar la OPENWEATHER_APP_ID en el archivo .env.

OPENWEATHER_APP_ID = config("OPENWEATHER_APP_ID")


def get_weather_report(city):
    res = requests.get(
        f"http://api.openweathermap.org/data/2.5/weather?q={city}&appid={OPENWEATHER_APP_ID}&units=metric").json()
    weather = res["weather"][0]["main"]
    temperature = res["main"]["temp"]
    feels_like = res["main"]["feels_like"]
    return weather, f"{temperature}℃", f"{feels_like}℃"

Según la OpenWeatherMap API, necesitamos realizar una solicitud GET en la URL mencionada anteriormente con el nombre de la ciudad. Obtendremos una respuesta JSON como:

{
    "coord": {
        "lon": 85,
        "lat": 24.7833
    },
    "weather": [
        {
            "id": 721,
            "main": "Haze",
            "description": "haze",
            "icon": "50d"
        }
    ],
    "base": "stations",
    "main": {
        "temp": 26.95,
        "feels_like": 26.64,
        "temp_min": 26.95,
        "temp_max": 26.95,
        "pressure": 1011,
        "humidity": 36
    },
    "visibility": 3000,
    "wind": {
        "speed": 2.57,
        "deg": 310
    },
    "clouds": {
        "all": 57
    },
    "dt": 1637227634,
    "sys": {
        "type": 1,
        "id": 9115,
        "country": "IN",
        "sunrise": 1637195904,
        "sunset": 1637235130
    },
    "timezone": 19800,
    "id": 1271439,
    "name": "Gaya",
    "cod": 200
}

Solo necesitamos weather, temperature, y feels_like de la respuesta anterior.

Como agregar la función para obtener las películas en tendencia

Para obtener las peliculas en tendencia, usaremos la The Movie Database (TMDB) API. Registrate con una cuenta gratuita para obtener la clave API. Agrégala  TMDB_API_KEY al archivo .env.

TMDB_API_KEY = config("TMDB_API_KEY")


def get_trending_movies():
    trending_movies = []
    res = requests.get(
        f"https://api.themoviedb.org/3/trending/movie/day?api_key={TMDB_API_KEY}").json()
    results = res["results"]
    for r in results:
        trending_movies.append(r["original_title"])
    return trending_movies[:5]

Justo como hicimos con los titulares, crearemos una lista llamada trending_movies. Entonces, según la API TMDB, haremos una solicitud GET. Uan respuesta JSON como muestra se ve de la siguiente manera:

{
  "page": 1,
  "results": [
    {
      "video": false,
      "vote_average": 7.9,
      "overview": "Shang-Chi must confront the past he thought he left behind when he is drawn into the web of the mysterious Ten Rings organization.",
      "release_date": "2021-09-01",
      "title": "Shang-Chi and the Legend of the Ten Rings",
      "adult": false,
      "backdrop_path": "/cinER0ESG0eJ49kXlExM0MEWGxW.jpg",
      "vote_count": 2917,
      "genre_ids": [28, 12, 14],
      "id": 566525,
      "original_language": "en",
      "original_title": "Shang-Chi and the Legend of the Ten Rings",
      "poster_path": "/1BIoJGKbXjdFDAqUEiA2VHqkK1Z.jpg",
      "popularity": 9559.446,
      "media_type": "movie"
    },
    {
      "adult": false,
      "backdrop_path": "/dK12GIdhGP6NPGFssK2Fh265jyr.jpg",
      "genre_ids": [28, 35, 80, 53],
      "id": 512195,
      "original_language": "en",
      "original_title": "Red Notice",
      "overview": "An Interpol-issued Red Notice is a global alert to hunt and capture the world's most wanted. But when a daring heist brings together the FBI's top profiler and two rival criminals, there's no telling what will happen.",
      "poster_path": "/wdE6ewaKZHr62bLqCn7A2DiGShm.jpg",
      "release_date": "2021-11-04",
      "title": "Red Notice",
      "video": false,
      "vote_average": 6.9,
      "vote_count": 832,
      "popularity": 1990.503,
      "media_type": "movie"
    },
    {
      "genre_ids": [12, 28, 53],
      "original_language": "en",
      "original_title": "No Time to Die",
      "poster_path": "/iUgygt3fscRoKWCV1d0C7FbM9TP.jpg",
      "video": false,
      "vote_average": 7.6,
      "overview": "Bond has left active service and is enjoying a tranquil life in Jamaica. His peace is short-lived when his old friend Felix Leiter from the CIA turns up asking for help. The mission to rescue a kidnapped scientist turns out to be far more treacherous than expected, leading Bond onto the trail of a mysterious villain armed with dangerous new technology.",
      "id": 370172,
      "vote_count": 1804,
      "title": "No Time to Die",
      "adult": false,
      "backdrop_path": "/1953j0QEbtN17WFFTnJHIm6bn6I.jpg",
      "release_date": "2021-09-29",
      "popularity": 4639.439,
      "media_type": "movie"
    },
    {
      "poster_path": "/5pVJ9SuuO72IgN6i9kMwQwnhGHG.jpg",
      "video": false,
      "vote_average": 0,
      "overview": "Peter Parker is unmasked and no longer able to separate his normal life from the high-stakes of being a Super Hero. When he asks for help from Doctor Strange the stakes become even more dangerous, forcing him to discover what it truly means to be Spider-Man.",
      "release_date": "2021-12-15",
      "id": 634649,
      "adult": false,
      "backdrop_path": "/vK18znei8Uha2z7ZhZtBa40HIrm.jpg",
      "vote_count": 0,
      "genre_ids": [28, 12, 878],
      "title": "Spider-Man: No Way Home",
      "original_language": "en",
      "original_title": "Spider-Man: No Way Home",
      "popularity": 1084.815,
      "media_type": "movie"
    },
    {
      "video": false,
      "vote_average": 6.8,
      "overview": "After finding a host body in investigative reporter Eddie Brock, the alien symbiote must face a new enemy, Carnage, the alter ego of serial killer Cletus Kasady.",
      "release_date": "2021-09-30",
      "adult": false,
      "backdrop_path": "/70nxSw3mFBsGmtkvcs91PbjerwD.jpg",
      "vote_count": 1950,
      "genre_ids": [878, 28, 12],
      "id": 580489,
      "original_language": "en",
      "original_title": "Venom: Let There Be Carnage",
      "poster_path": "/rjkmN1dniUHVYAtwuV3Tji7FsDO.jpg",
      "title": "Venom: Let There Be Carnage",
      "popularity": 4527.568,
      "media_type": "movie"
    }
  ],
  "total_pages": 1000,
  "total_results": 20000
}

De la respuesta anterior, solo necesitamos el título de la película. Obtenemos results la cual es una lista y entonces iteramos sobre ella para obtener el título de la película y anexarlo a la lista trending_movies. Al final, mostramos los primeros cinco elementos de la lista.

Como agregar la función para hacer chistes aleatoriamente

Para obtener un chiste al azar, necesitamos realizar una solicitud GET en la siguiente URL: https://icanhazdadjoke.com/.

def get_random_joke():
    headers = {
        'Accept': 'application/json'
    }
    res = requests.get("https://icanhazdadjoke.com/", headers=headers).json()
    return res["joke"]

Como agregar la función para obtener un consejo al azar

Para obtener algún consejo al azar, usaremos la Advice Slip API.

def get_random_advice():
    res = requests.get("https://api.adviceslip.com/advice").json()
    return res['slip']['advice']

Como crear el comando principal

Para ejecutar el proyecto, necesitamos crear nuestro comando principal. Crea un archivo main.py y agrégale el siguiente código:

import requests
from functions.online_ops import find_my_ip, get_latest_news, get_random_advice, get_random_joke, get_trending_movies, get_weather_report, play_on_youtube, search_on_google, search_on_wikipedia, send_email, send_whatsapp_message
from functions.os_ops import open_calculator, open_camera, open_cmd, open_notepad, open_discord
from pprint import pprint


if __name__ == '__main__':
    greet_user()
    while True:
        query = take_user_input().lower()

        if 'open notepad' in query:
            open_notepad()

        elif 'open discord' in query:
            open_discord()

        elif 'open command prompt' in query or 'open cmd' in query:
            open_cmd()

        elif 'open camera' in query:
            open_camera()

        elif 'open calculator' in query:
            open_calculator()

        elif 'ip address' in query:
            ip_address = find_my_ip()
            speak(f'Su Dirección IP es {ip_address}.\n Para su comodidad la estoy mostrado en la pantalla señor.')
            print(f'Tu direccion IP es {ip_address}')

        elif 'wikipedia' in query:
            speak('¿Qué quiere buscar en Wikipedia, señor?')
            search_query = take_user_input().lower()
            results = search_on_wikipedia(search_query)
            speak(f"De acuerdo con Wikipedia, {results}")
            speak("Para su comodidad, mostraré en pantalla los resultados, señor.")
            print(results)

        elif 'youtube' in query:
            speak('¿Que desea ver en YouTube, señor?')
            video = take_user_input().lower()
            play_on_youtube(video)

        elif 'search on google' in query:
            speak('¿Qué desea buscar en Google, señor?')
            query = take_user_input().lower()
            search_on_google(query)

        elif "send whatsapp message" in query:
            speak('¿A qué número debería enviar el mensaje señor?, por favor, digítelo en la consola: ')
            number = input("Ingrese el número: ")
            speak("¿Cúal es el mensaje, señor?")
            message = take_user_input().lower()
            send_whatsapp_message(number, message)
            speak("El mensaje ha sido enviado, señor.")

        elif "send an email" in query:
            speak("¿A qué dirección de correo la envío, señor? Por favor, ingresela en la consola: ")
            receiver_address = input("Ingrese la dirección de correo electrónico: ")
            speak("¿Cuál es el asunto, señor?")
            subject = take_user_input().capitalize()
            speak("¿Cuál es el mensaje, señor?")
            message = take_user_input().capitalize()
            if send_email(receiver_address, subject, message):
                speak("El correo ha sido enviado, señor.")
            else:
                speak("Algo ha salido mal mientras estaba enviando el correo, por favor revise el registro de errores, señor.")

        elif 'joke' in query:
            speak(f"Espero le guste este, señor.")
            joke = get_random_joke()
            speak(joke)
            speak("Para su goce, se lo mostraré en la pantalla, señor.")
            pprint(joke)

        elif "advice" in query:
            speak(f"Acá hay un consejo para usted, señor.")
            advice = get_random_advice()
            speak(advice)
            speak("Para su disfrute, lo estoy mostrando en la pantalla, señor.")
            pprint(advice)

        elif "trending movies" in query:
            speak(f"Algunas de las películas en tendencia son: {get_trending_movies()}")
            speak("Para su mejor comprensión, están en la pantalla, señor.")
            print(*get_trending_movies(), sep='\n')

        elif 'news' in query:
            speak(f"Estoy leyendo los útimos titulares de las noticias, señor.")
            speak(get_latest_news())
            speak("Para su comodidad, las mostraré en la pantalla, señor.")
            print(*get_latest_news(), sep='\n')

        elif 'weather' in query:
            ip_address = find_my_ip()
            city = requests.get(f"https://ipapi.co/{ip_address}/city/").text
            speak(f"Obteniendo el reporte del clima en su ciudad {city}")
            weather, temperature, feels_like = get_weather_report(city)
            speak(f"La temperatura actual es {temperature}, pero se siente más como {feels_like}")
            speak(f"Además, el reporte menciona acerca de {weather}")
            speak("Para vuestra información, se la mostraré en la pantalla, señor.")
            print(f"Description: {weather}\nTemperature: {temperature}\nFeels like: {feels_like}")

Mientras que el código anterior parece bastante extenso, es bastante simple y sencillo de entender.

Si observas con detenimiento, todo lo que se hizo es importar los módulos requeridos y las funciones en línea y sin conexión. Entonces, dentro del comando principal, la primera cosa que se hace es dar la bienvenida al usuario usando la función greet_user().

Luego, ejecutamos un ciclo while para continuamente obtener información del usuario usando la función take_user_input(), Ya que nuestra cadena query está, podemos agregar un filtro if-else  para revisar diferentes condiciones en la cadena query.

Nota: Para Python 3.10, puedes usar Python Match Case en lugar del filtro if-else.

Para ejecutar el programa, puedes usar la siguiente instrucción:

$ python main.py

Conclusión

Acabamos de crear nuestro propio asistente personal virtual con la ayuda de Python. Puedes agregar más características a la aplicación si lo deseas. Puedes agregar este proyecto a tu portafolio o ¡solo hacerlo por diversión!

Para el código completo, revisa el siguiente repositorio en GitHub: https://github.com/ashutoshkrris/Virtual-Personal-Assistant-using-Python