diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c246d5a --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +References/ +env/ +.vscode/ +__pycache__/ +experiments/ +*.log +config.py +*.pickle +credentials.json +*note.txt +*.png +*.mp4 +*dbs.txt \ No newline at end of file diff --git a/Jarvis/__init__.py b/Jarvis/__init__.py new file mode 100644 index 0000000..a46e419 --- /dev/null +++ b/Jarvis/__init__.py @@ -0,0 +1,149 @@ +import speech_recognition as sr +import pyttsx3 + +from Jarvis.features import date_time +from Jarvis.features import launch_app +from Jarvis.features import website_open +from Jarvis.features import weather +from Jarvis.features import wikipedia +from Jarvis.features import news +from Jarvis.features import send_email +from Jarvis.features import google_search +from Jarvis.features import google_calendar +from Jarvis.features import note +from Jarvis.features import system_stats +from Jarvis.features import loc + + +engine = pyttsx3.init('sapi5') +voices = engine.getProperty('voices') +engine.setProperty('voices', voices[0].id) + +class JarvisAssistant: + def __init__(self): + pass + + def mic_input(self): + """ + Fetch input from mic + return: user's voice input as text if true, false if fail + """ + try: + r = sr.Recognizer() + # r.pause_threshold = 1 + # r.adjust_for_ambient_noise(source, duration=1) + with sr.Microphone() as source: + print("Listening....") + r.energy_threshold = 4000 + audio = r.listen(source) + try: + print("Recognizing...") + command = r.recognize_google(audio, language='en-in').lower() + print(f'You said: {command}') + except: + print('Please try again') + command = self.mic_input() + return command + except Exception as e: + print(e) + return False + + + def tts(self, text): + """ + Convert any text to speech + :param text: text(String) + :return: True/False (Play sound if True otherwise write exception to log and return False) + """ + try: + engine.say(text) + engine.runAndWait() + engine.setProperty('rate', 175) + return True + except: + t = "Sorry I couldn't understand and handle this input" + print(t) + return False + + def tell_me_date(self): + + return date_time.date() + + def tell_time(self): + + return date_time.time() + + def launch_any_app(self, path_of_app): + """ + Launch any windows application + :param path_of_app: path of exe + :return: True is success and open the application, False if fail + """ + return launch_app.launch_app(path_of_app) + + def website_opener(self, domain): + """ + This will open website according to domain + :param domain: any domain, example "youtube.com" + :return: True if success, False if fail + """ + return website_open.website_opener(domain) + + + def weather(self, city): + """ + Return weather + :param city: Any city of this world + :return: weather info as string if True, or False + """ + try: + res = weather.fetch_weather(city) + except Exception as e: + print(e) + res = False + return res + + def tell_me(self, topic): + """ + Tells about anything from wikipedia + :param topic: any string is valid options + :return: First 500 character from wikipedia if True, False if fail + """ + return wikipedia.tell_me_about(topic) + + def news(self): + """ + Fetch top news of the day from google news + :return: news list of string if True, False if fail + """ + return news.get_news() + + def send_mail(self, sender_email, sender_password, receiver_email, msg): + + return send_email.mail(sender_email, sender_password, receiver_email, msg) + + def google_calendar_events(self, text): + service = google_calendar.authenticate_google() + date = google_calendar.get_date(text) + + if date: + return google_calendar.get_events(date, service) + else: + pass + + def search_anything_google(self, command): + google_search.google_search(command) + + def take_note(self, text): + note.note(text) + + def system_info(self): + return system_stats.system_stats() + + def location(self, location): + current_loc, target_loc, distance = loc.loc(location) + return current_loc, target_loc, distance + + def my_location(self): + city, state, country = loc.my_location() + return city, state, country \ No newline at end of file diff --git a/Jarvis/features/__init__.py b/Jarvis/features/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Jarvis/features/date_time.py b/Jarvis/features/date_time.py new file mode 100644 index 0000000..b544c87 --- /dev/null +++ b/Jarvis/features/date_time.py @@ -0,0 +1,27 @@ +import datetime + + +def date(): + """ + Just return date as string + :return: date if success, False if fail + """ + try: + date = datetime.datetime.now().strftime("%b %d %Y") + except Exception as e: + print(e) + date = False + return date + + +def time(): + """ + Just return time as string + :return: time if success, False if fail + """ + try: + time = datetime.datetime.now().strftime("%H:%M:%S") + except Exception as e: + print(e) + time = False + return time \ No newline at end of file diff --git a/Jarvis/features/google_calendar.py b/Jarvis/features/google_calendar.py new file mode 100644 index 0000000..206d664 --- /dev/null +++ b/Jarvis/features/google_calendar.py @@ -0,0 +1,142 @@ +from __future__ import print_function +import datetime, pytz, pyttsx3, pickle, os.path +from googleapiclient.discovery import build +from google_auth_oauthlib.flow import InstalledAppFlow +from google.auth.transport.requests import Request + + + +MONTHS = ["january", "february", "march", "april", "may", "june", "july", "august", "september", "october", "november", "december"] +DAYS = ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"] +DAY_EXTENSIONS = ["rd", "th", "st", "nd"] + + + + + +def speak(text): + engine = pyttsx3.init('sapi5') + voices = engine.getProperty('voices') + engine.setProperty('voices', voices[0].id) + engine.say(text) + engine.runAndWait() + engine.setProperty('rate', 180) + + + + +# If modifying these scopes, delete the file token.pickle. +SCOPES = ['https://www.googleapis.com/auth/calendar.readonly'] + + +def authenticate_google(): + """Shows basic usage of the Google Calendar API. + Prints the start and name of the next 10 events on the user's calendar. + """ + creds = None + # The file token.pickle stores the user's access and refresh tokens, and is + # created automatically when the authorization flow completes for the first + # time. + if os.path.exists('token.pickle'): + with open('token.pickle', 'rb') as token: + creds = pickle.load(token) + # If there are no (valid) credentials available, let the user log in. + if not creds or not creds.valid: + if creds and creds.expired and creds.refresh_token: + creds.refresh(Request()) + else: + flow = InstalledAppFlow.from_client_secrets_file( + 'credentials.json', SCOPES) + creds = flow.run_local_server(port=0) + # Save the credentials for the next run + with open('token.pickle', 'wb') as token: + pickle.dump(creds, token) + + service = build('calendar', 'v3', credentials=creds) + + return service + +def get_events(day, service): + # Call the Calendar API + date = datetime.datetime.combine(day, datetime.datetime.min.time()) + end_date = datetime.datetime.combine(day, datetime.datetime.max.time()) + utc = pytz.UTC + date = date.astimezone(utc) + end_date = end_date.astimezone(utc) + + + + events_result = service.events().list(calendarId='primary', timeMin=date.isoformat(), timeMax=end_date.isoformat(), + singleEvents=True, + orderBy='startTime').execute() + events = events_result.get('items', []) + + if not events: + speak('No upcoming events found.') + else: + speak(f"You have {len(events)} events on this day.") + + for event in events: + start = event['start'].get('dateTime', event['start'].get('date')) + print(start, event['summary']) + start_time = str(start.split("T")[1].split("+")[0]) # get the hour the event starts + if int(start_time.split(":")[0]) < 12: # if the event is in the morning + start_time = start_time + "am" + else: + start_time = str(int(start_time.split(":")[0])-12) # convert 24 hour time to regular + start_time = start_time + "pm" + + speak(event["summary"] + " at " + start_time) + + +def get_date(text): + today = datetime.date.today() + + if text.count("today") > 0: + return today + + day = -1 + day_of_week = -1 + month = -1 + year = today.year + + for word in text.split(): + if word in MONTHS: + month = MONTHS.index(word) + 1 + elif word in DAYS: + day_of_week = DAYS.index(word) + elif word.isdigit(): + day = int(word) + else: + for ext in DAY_EXTENSIONS: + found = word.find(ext) + if found > 0: + try: + day = int(word[:found]) + except: + pass + + if month < today.month and month != -1: + year = year+1 + + + if month == -1 and day != -1: + if day < today.day: + month = today.month + 1 + else: + month = today.month + + + if month == -1 and day == -1 and day_of_week != -1: + current_day_of_week = today.weekday() + dif = day_of_week - current_day_of_week + + if dif < 0: + dif += 7 + if text.count("next") >= 1: + dif += 7 + + return today + datetime.timedelta(dif) + + if day != -1: + return datetime.date(month=month, day=day, year=year) \ No newline at end of file diff --git a/Jarvis/features/google_search.py b/Jarvis/features/google_search.py new file mode 100644 index 0000000..397ba93 --- /dev/null +++ b/Jarvis/features/google_search.py @@ -0,0 +1,31 @@ +from selenium import webdriver +from selenium.webdriver.common.keys import Keys +import re, pyttsx3 + + + +def speak(text): + engine = pyttsx3.init('sapi5') + voices = engine.getProperty('voices') + engine.setProperty('voices', voices[0].id) + engine.say(text) + engine.runAndWait() + engine.setProperty('rate', 180) + + +def google_search(command): + + reg_ex = re.search('search google for (.*)', command) + search_for = command.split("for", 1)[1] + url = 'https://www.google.com/' + if reg_ex: + subgoogle = reg_ex.group(1) + url = url + 'r/' + subgoogle + speak("Okay sir!") + speak(f"Searching for {subgoogle}") + driver = webdriver.Chrome( + executable_path='driver/chromedriver.exe') + driver.get('https://www.google.com') + search = driver.find_element_by_name('q') + search.send_keys(str(search_for)) + search.send_keys(Keys.RETURN) \ No newline at end of file diff --git a/Jarvis/features/gui.py b/Jarvis/features/gui.py new file mode 100644 index 0000000..eb6196d --- /dev/null +++ b/Jarvis/features/gui.py @@ -0,0 +1,85 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'gui.ui' +# +# Created by: PyQt5 UI code generator 5.15.2 +# +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_MainWindow(object): + def setupUi(self, MainWindow): + MainWindow.setObjectName("Jarvis 2.0") + MainWindow.resize(1440, 900) + self.centralwidget = QtWidgets.QWidget(MainWindow) + self.centralwidget.setObjectName("centralwidget") + self.label = QtWidgets.QLabel(self.centralwidget) + self.label.setGeometry(QtCore.QRect(0, 0, 1440, 900)) + self.label.setText("") + self.label.setPixmap(QtGui.QPixmap("Jarvis/utils/images/live_wallpaper.gif")) + self.label.setScaledContents(True) + self.label.setObjectName("label") + self.pushButton = QtWidgets.QPushButton(self.centralwidget) + self.pushButton.setGeometry(QtCore.QRect(1180, 800, 101, 51)) + self.pushButton.setStyleSheet("background-color: rgb(0, 170, 255);\n" +"font: 75 18pt \"MS Shell Dlg 2\";") + self.pushButton.setObjectName("pushButton") + self.pushButton_2 = QtWidgets.QPushButton(self.centralwidget) + self.pushButton_2.setGeometry(QtCore.QRect(1310, 800, 101, 51)) + self.pushButton_2.setStyleSheet("background-color:rgb(255, 0, 0);\n" +"font: 75 18pt \"MS Shell Dlg 2\";") + self.pushButton_2.setObjectName("pushButton_2") + self.label_2 = QtWidgets.QLabel(self.centralwidget) + self.label_2.setGeometry(QtCore.QRect(10, 10, 401, 91)) + self.label_2.setText("") + self.label_2.setPixmap(QtGui.QPixmap("Jarvis/utils/images/initiating.gif")) + self.label_2.setObjectName("label_2") + self.textBrowser = QtWidgets.QTextBrowser(self.centralwidget) + self.textBrowser.setGeometry(QtCore.QRect(640, 30, 291, 61)) + self.textBrowser.setStyleSheet("font: 75 16pt \"MS Shell Dlg 2\";\n" +"background-color:transparent;\ncolor:white;" +"border-radius:none;\n" +"") + self.textBrowser.setObjectName("textBrowser") + self.textBrowser_2 = QtWidgets.QTextBrowser(self.centralwidget) + self.textBrowser_2.setGeometry(QtCore.QRect(930, 30, 291, 61)) + self.textBrowser_2.setStyleSheet("font: 75 16pt \"MS Shell Dlg 2\";\n" +"background-color:transparent;\ncolor:white;" +"border-radius:none;") + self.textBrowser_2.setObjectName("textBrowser_2") + self.textBrowser_3 = QtWidgets.QTextBrowser(self.centralwidget) + self.textBrowser_3.setGeometry(QtCore.QRect(1000, 500, 431, 281)) + self.textBrowser_3.setStyleSheet("font: 11pt \"MS Shell Dlg 2\";\n" +"background-color:transparent;\ncolor:white;") + self.textBrowser_3.setObjectName("textBrowser_3") + MainWindow.setCentralWidget(self.centralwidget) + self.menubar = QtWidgets.QMenuBar(MainWindow) + self.menubar.setGeometry(QtCore.QRect(0, 0, 1440, 26)) + self.menubar.setObjectName("menubar") + MainWindow.setMenuBar(self.menubar) + self.statusbar = QtWidgets.QStatusBar(MainWindow) + self.statusbar.setObjectName("statusbar") + MainWindow.setStatusBar(self.statusbar) + + self.retranslateUi(MainWindow) + QtCore.QMetaObject.connectSlotsByName(MainWindow) + + def retranslateUi(self, MainWindow): + _translate = QtCore.QCoreApplication.translate + MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow")) + self.pushButton.setText(_translate("MainWindow", "Run")) + self.pushButton_2.setText(_translate("MainWindow", "Exit")) + + +if __name__ == "__main__": + import sys + app = QtWidgets.QApplication(sys.argv) + MainWindow = QtWidgets.QMainWindow() + ui = Ui_MainWindow() + ui.setupUi(MainWindow) + MainWindow.show() + sys.exit(app.exec_()) diff --git a/Jarvis/features/launch_app.py b/Jarvis/features/launch_app.py new file mode 100644 index 0000000..307806c --- /dev/null +++ b/Jarvis/features/launch_app.py @@ -0,0 +1,9 @@ +import subprocess + +def launch_app(path_of_app): + try: + subprocess.call([path_of_app]) + return True + except Exception as e: + print(e) + return False \ No newline at end of file diff --git a/Jarvis/features/loc.py b/Jarvis/features/loc.py new file mode 100644 index 0000000..6281072 --- /dev/null +++ b/Jarvis/features/loc.py @@ -0,0 +1,37 @@ +import webbrowser, requests +from geopy.geocoders import Nominatim +from geopy.distance import great_circle +import geocoder + +def loc(place): + webbrowser.open("http://www.google.com/maps/place/" + place + "") + geolocator = Nominatim(user_agent="myGeocoder") + location = geolocator.geocode(place, addressdetails=True) + target_latlng = location.latitude, location.longitude + location = location.raw['address'] + target_loc = {'city': location.get('city', ''), + 'state': location.get('state', ''), + 'country': location.get('country', '')} + + current_loc = geocoder.ip('me') + current_latlng = current_loc.latlng + + distance = str(great_circle(current_latlng, target_latlng)) + distance = str(distance.split(' ',1)[0]) + distance = round(float(distance), 2) + + return current_loc, target_loc, distance + +def my_location(): + ip_add = requests.get('https://api.ipify.org').text + url = 'https://get.geojs.io/v1/ip/geo/' + ip_add + '.json' + geo_requests = requests.get(url) + geo_data = geo_requests.json() + city = geo_data['city'] + state = geo_data['region'] + country = geo_data['country'] + + return city, state,country + + + diff --git a/Jarvis/features/news.py b/Jarvis/features/news.py new file mode 100644 index 0000000..26914de --- /dev/null +++ b/Jarvis/features/news.py @@ -0,0 +1,19 @@ +import requests +import json + + + +def get_news(): + url = 'http://newsapi.org/v2/top-headlines?sources=the-times-of-india&apiKey=ae5ccbe2006a4debbe6424d7e4b569ec' + news = requests.get(url).text + news_dict = json.loads(news) + articles = news_dict['articles'] + try: + + return articles + except: + return False + + +def getNewsUrl(): + return 'http://newsapi.org/v2/top-headlines?sources=the-times-of-india&apiKey=ae5ccbe2006a4debbe6424d7e4b569ec' diff --git a/Jarvis/features/note.py b/Jarvis/features/note.py new file mode 100644 index 0000000..2b2c50d --- /dev/null +++ b/Jarvis/features/note.py @@ -0,0 +1,11 @@ +import subprocess +import datetime + +def note(text): + date = datetime.datetime.now() + file_name = str(date).replace(":", "-") + "-note.txt" + with open(file_name, "w") as f: + f.write(text) + notepad = "C://Program Files (x86)//Notepad++//notepad++.exe" + subprocess.Popen([notepad, file_name]) + diff --git a/Jarvis/features/send_email.py b/Jarvis/features/send_email.py new file mode 100644 index 0000000..3c06ff2 --- /dev/null +++ b/Jarvis/features/send_email.py @@ -0,0 +1,15 @@ +import smtplib + + +def mail(sender_email, sender_password, receiver_email, msg): + try: + mail = smtplib.SMTP('smtp.gmail.com', 587) + mail.ehlo() + mail.starttls() + mail.login(sender_email, sender_password) + mail.sendmail(sender_email, receiver_email, msg) + mail.close() + return True + except Exception as e: + print(e) + return False \ No newline at end of file diff --git a/Jarvis/features/system_stats.py b/Jarvis/features/system_stats.py new file mode 100644 index 0000000..663c977 --- /dev/null +++ b/Jarvis/features/system_stats.py @@ -0,0 +1,21 @@ +import psutil, pyttsx3, math + +def convert_size(size_bytes): + if size_bytes == 0: + return "0B" + size_name = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB") + i = int(math.floor(math.log(size_bytes, 1024))) + p = math.pow(1024, i) + s = round(size_bytes / p, 2) + print("%s %s" % (s, size_name[i])) + return "%s %s" % (s, size_name[i]) + + +def system_stats(): + cpu_stats = str(psutil.cpu_percent()) + battery_percent = psutil.sensors_battery().percent + memory_in_use = convert_size(psutil.virtual_memory().used) + total_memory = convert_size(psutil.virtual_memory().total) + final_res = f"Currently {cpu_stats} percent of CPU, {memory_in_use} of RAM out of total {total_memory} is being used and battery level is at {battery_percent} percent" + return final_res + diff --git a/Jarvis/features/weather.py b/Jarvis/features/weather.py new file mode 100644 index 0000000..9fe9d49 --- /dev/null +++ b/Jarvis/features/weather.py @@ -0,0 +1,42 @@ +import requests +from Jarvis.config import config + + + +def fetch_weather(city): + """ + City to weather + :param city: City + :return: weather + """ + api_key = config.weather_api_key + units_format = "&units=metric" + + base_url = "http://api.openweathermap.org/data/2.5/weather?q=" + complete_url = base_url + city + "&appid=" + api_key + units_format + + response = requests.get(complete_url) + + city_weather_data = response.json() + + if city_weather_data["cod"] != "404": + main_data = city_weather_data["main"] + weather_description_data = city_weather_data["weather"][0] + weather_description = weather_description_data["description"] + current_temperature = main_data["temp"] + current_pressure = main_data["pressure"] + current_humidity = main_data["humidity"] + wind_data = city_weather_data["wind"] + wind_speed = wind_data["speed"] + + final_response = f""" + The weather in {city} is currently {weather_description} + with a temperature of {current_temperature} degree celcius, + atmospheric pressure of {current_pressure} hectoPascals, + humidity of {current_humidity} percent + and wind speed reaching {wind_speed} kilometers per hour""" + + return final_response + + else: + return "Sorry Sir, I couldn't find the city in my database. Please try again" \ No newline at end of file diff --git a/Jarvis/features/website_open.py b/Jarvis/features/website_open.py new file mode 100644 index 0000000..7a4f8c7 --- /dev/null +++ b/Jarvis/features/website_open.py @@ -0,0 +1,10 @@ +import webbrowser + +def website_opener(domain): + try: + url = 'https://www.' + domain + webbrowser.open(url) + return True + except Exception as e: + print(e) + return False \ No newline at end of file diff --git a/Jarvis/features/wikipedia.py b/Jarvis/features/wikipedia.py new file mode 100644 index 0000000..2ee4d1e --- /dev/null +++ b/Jarvis/features/wikipedia.py @@ -0,0 +1,13 @@ +import wikipedia +import re + +def tell_me_about(topic): + try: + # info = str(ny.content[:500].encode('utf-8')) + # res = re.sub('[^a-zA-Z.\d\s]', '', info)[1:] + res = wikipedia.summary(topic, sentences=3) + + return res + except Exception as e: + print(e) + return False diff --git a/Jarvis/features/youtube_search.py b/Jarvis/features/youtube_search.py new file mode 100644 index 0000000..7c5efa5 --- /dev/null +++ b/Jarvis/features/youtube_search.py @@ -0,0 +1,22 @@ +import webbrowser, urllib, re +import urllib.parse +import urllib.request + +domain = input("Enter the song name: ") +song = urllib.parse.urlencode({"search_query" : domain}) +print("Song" + song) + +# fetch the ?v=query_string +result = urllib.request.urlopen("http://www.youtube.com/results?" + song) +print(result) + +# make the url of the first result song +search_results = re.findall(r'href=\"\/watch\?v=(.{4})', result.read().decode()) +print(search_results) + +# make the final url of song selects the very first result from youtube result +url = "http://www.youtube.com/watch?v="+str(search_results) + +# play the song using webBrowser module which opens the browser +# webbrowser.open(url, new = 1) +webbrowser.open_new(url) \ No newline at end of file diff --git a/Jarvis/utils/images/initiating.gif b/Jarvis/utils/images/initiating.gif new file mode 100644 index 0000000..949af7f Binary files /dev/null and b/Jarvis/utils/images/initiating.gif differ diff --git a/Jarvis/utils/images/live_wallpaper.gif b/Jarvis/utils/images/live_wallpaper.gif new file mode 100644 index 0000000..819ba31 Binary files /dev/null and b/Jarvis/utils/images/live_wallpaper.gif differ diff --git a/Jarvis/utils/images/loading.gif b/Jarvis/utils/images/loading.gif new file mode 100644 index 0000000..7beabc1 Binary files /dev/null and b/Jarvis/utils/images/loading.gif differ diff --git a/Jarvis/utils/images/loading_1.gif b/Jarvis/utils/images/loading_1.gif new file mode 100644 index 0000000..d835a89 Binary files /dev/null and b/Jarvis/utils/images/loading_1.gif differ diff --git a/Jarvis/utils/images/program_load.gif b/Jarvis/utils/images/program_load.gif new file mode 100644 index 0000000..753d0a0 Binary files /dev/null and b/Jarvis/utils/images/program_load.gif differ diff --git a/README.md b/README.md index d15543f..da6dec1 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,86 @@ -# Jarvis -JARVIS (Just a Rather Very Intelligent System) +# JARVIS (Just a Rather Very Intelligent System) + +#### This was my attempt to make a voice assistant similar to JARVIS (in iron man movie) +#### Let's be honest, it's not as intelligent as in the movie, but it can do a lot of cool things and automate your daily tasks you do on your personal computers/laptops. + +## Built with + + + + +## Features + +It can do a lot of cool things, some of them being: + +- Greet user +- Tell current time and date +- Launch applications/softwares +- Open any website +- Tells about weather of any city +- Open location of any place plus tells the distance between your place and queried place +- Tells your current system status (RAM Usage, battery health, CPU usage) +- Tells about your upcoming events (Google Calendar) +- Tells about any person (via Wikipedia) +- Can search anything on Google +- Can play any song on YouTube +- Tells top headlines (via Times of India) +- Plays music +- Send email (with subject and content) +- Calculate any mathematical expression (example: Jarvis, calculate x + 135 - 234 = 345) +- Answer any generic question (via Wolframalpha) +- Take important note in notepad +- Tells a random joke +- Tells your IP address +- Can switch the window +- Can take screenshot and save it with custom filename +- Can hide all files in a folder and also make them visible again +- Has a cool Graphical User Interface + +## API Keys +To run this program you will require a bunch of API keys. Register your API key by clicking the following links + +- [OpenWeatherMap API](https://openweathermap.org/api) +- [Wolframalpha](https://www.wolframalpha.com/) +- [Google Calendar API](https://developers.google.com/calendar/auth) + +## Installation + +- First clone the repo +- Make a config.py file and include the following in it: + ```weather_api_key = "" + email = "" + email_password = "" + wolframalpha_id = "" +- Copy the config.py file in Jarvis>config folder +- Make a new python environment + If you are using anaconda just type ```conda create -n jarvis python==3.8.5 ``` in anaconda prompt +- To activate the environment ``` conda activate jarvis ``` +- Navigate to the directory of your project +- Install all the requirements by just hitting ``` pip install -r requirements.txt ``` +- Install PyAudio from wheel file by following instructions given [here](https://stackoverflow.com/a/55630212) +- Run the program by ``` python main.py ``` +- Enjoy !!!! + +## Code Structure + + + ├── driver + ├── Jarvis # Main folder for features + │ ├── config # Contains all secret API Keys + │ ├── features # All functionalities of JARVIS + │ └── utils # GUI images + ├── __init__.py # Definition of feature's functions + ├── gui.ui # GUI file (in .ui format) + ├── main.py # main driver program of Jarvis + ├── requirements.txt # all dependencies of the program + +- The code structure if pretty simple. The code is completely modularized and is highly customizable +- To add a new feature: + - Make a new file in features folder, write the feature's function you want to include + - Add the function's definition to __init__.py + - Add the voice commands through which you want to invoke the function + +## Future Improvements +- Generalized conversations can be made possible by incorporating Natural Language Processing +- GUI can be made more nicer to look at and functional +- More functionalities can be added diff --git a/driver/chromedriver.exe b/driver/chromedriver.exe new file mode 100644 index 0000000..38ea5f5 Binary files /dev/null and b/driver/chromedriver.exe differ diff --git a/gui.ui b/gui.ui new file mode 100644 index 0000000..c2dad24 --- /dev/null +++ b/gui.ui @@ -0,0 +1,146 @@ + + + MainWindow + + + + 0 + 0 + 1440 + 900 + + + + MainWindow + + + + + + 0 + 0 + 1440 + 900 + + + + + + + Jarvis/utils/images/live_wallpaper.gif + + + true + + + + + + 1180 + 790 + 101 + 51 + + + + background-color: rgb(0, 170, 255); +font: 75 18pt "MS Shell Dlg 2"; + + + Run + + + + + + 1310 + 790 + 101 + 51 + + + + background-color:rgb(255, 0, 0); +font: 75 18pt "MS Shell Dlg 2"; + + + Exit + + + + + + 10 + 10 + 401 + 91 + + + + + + + Jarvis/utils/images/initiating.gif + + + + + + 640 + 30 + 291 + 61 + + + + font: 75 16pt "MS Shell Dlg 2"; +background-color:transparent; +border-radius:none; + + + + + + + 930 + 30 + 291 + 61 + + + + font: 75 16pt "MS Shell Dlg 2"; +background-color:transparent; +border-radius:none; + + + + + + 1000 + 500 + 431 + 281 + + + + font: 11pt "MS Shell Dlg 2"; +background-color:transparent; + + + + + + + 0 + 0 + 1440 + 26 + + + + + + + + diff --git a/main.py b/main.py new file mode 100644 index 0000000..68cd82f --- /dev/null +++ b/main.py @@ -0,0 +1,377 @@ +from Jarvis import JarvisAssistant +import re +import os +import random +import pprint +import datetime +import requests +import sys +import urllib.parse +import pyjokes +import time +import pyautogui +import pywhatkit +import wolframalpha +from PIL import Image +from PyQt5 import QtWidgets, QtCore, QtGui +from PyQt5.QtCore import QTimer, QTime, QDate, Qt +from PyQt5.QtGui import QMovie +from PyQt5.QtCore import * +from PyQt5.QtGui import * +from PyQt5.QtWidgets import * +from PyQt5.uic import loadUiType +from Jarvis.features.gui import Ui_MainWindow +from Jarvis.config import config + +obj = JarvisAssistant() + +# ================================ MEMORY =========================================================================================================== + +GREETINGS = ["hi jarvis", "jarvis", "wake up jarvis", "you there jarvis", "time to work jarvis", "hey jarvis", + "ok jarvis", "are you there"] +GREETINGS_RES = ["always there for you wasii", "i am ready wasii", + "your wish my command", "how can i help you wasii?", "i am online and ready wasii"] + +EMAIL_DIC = { + 'myself': 'support@hackerwasii.com', + 'my official email': 'support@hackerwasii.com', + 'my second email': 'support@hackerwasii.com', + 'my official mail': 'support@hackerwasii.com', + 'my second mail': 'support@hackerwasii.com' +} + +CALENDAR_STRS = ["what do i have", "do i have plans", "am i busy"] +# ======================================================================================================================================================= + + +def speak(text): + obj.tts(text) + + +app_id = config.wolframalpha_id + + +def computational_intelligence(question): + try: + client = wolframalpha.Client(app_id) + answer = client.query(question) + answer = next(answer.results).text + print(answer) + return answer + except: + speak("Sorry sir I couldn't fetch your question's answer. Please try again ") + return None + +def startup(): + speak("Initializing Jarvis, wait a moment wasii") + speak("Starting all systems applications") + speak("Installing and checking all drivers") + speak("Caliberating and examining all the core processors") + speak("Checking the internet connection") + speak("Wait a moment wasii") + speak("All drivers are up and running") + speak("All systems have been activated") + speak("Now I am online") + hour = int(datetime.datetime.now().hour) + if hour>=0 and hour<=12: + speak("Good Morning") + elif hour>12 and hour<18: + speak("Good afternoon") + else: + speak("Good evening") + c_time = obj.tell_time() + speak(f"Currently it is {c_time}") + speak("I am Jarvis. Online and ready sir wasii. Please tell me how may I help you") + + + + +def wish(): + hour = int(datetime.datetime.now().hour) + if hour>=0 and hour<=12: + speak("Good Morning") + elif hour>12 and hour<18: + speak("Good afternoon") + else: + speak("Good evening") + c_time = obj.tell_time() + speak(f"Currently it is {c_time}") + speak("I am Jarvis. Online and ready sir. Please tell me how may I help you") +# if __name__ == "__main__": + + +class MainThread(QThread): + def __init__(self): + super(MainThread, self).__init__() + + def run(self): + self.TaskExecution() + + def TaskExecution(self): + startup() + wish() + + while True: + command = obj.mic_input() + + if re.search('date', command): + date = obj.tell_me_date() + print(date) + speak(date) + + elif "time" in command: + time_c = obj.tell_time() + print(time_c) + speak(f"Sir the time is {time_c}") + + elif re.search('launch', command): + dict_app = { + 'chrome': 'C:/Program Files/Google/Chrome/Application/chrome' + } + + app = command.split(' ', 1)[1] + path = dict_app.get(app) + + if path is None: + speak('Application path not found') + print('Application path not found') + + else: + speak('Launching: ' + app + 'for you sir!') + obj.launch_any_app(path_of_app=path) + + elif command in GREETINGS: + speak(random.choice(GREETINGS_RES)) + + elif re.search('open', command): + domain = command.split(' ')[-1] + open_result = obj.website_opener(domain) + speak(f'Alright sir !! Opening {domain}') + print(open_result) + + elif re.search('weather', command): + city = command.split(' ')[-1] + weather_res = obj.weather(city=city) + print(weather_res) + speak(weather_res) + + elif re.search('tell me about', command): + topic = command.split(' ')[-1] + if topic: + wiki_res = obj.tell_me(topic) + print(wiki_res) + speak(wiki_res) + else: + speak( + "Sorry sir. I couldn't load your query from my database. Please try again") + + elif "buzzing" in command or "news" in command or "headlines" in command: + news_res = obj.news() + speak('Source: The Times Of India') + speak('Todays Headlines are..') + for index, articles in enumerate(news_res): + pprint.pprint(articles['title']) + speak(articles['title']) + if index == len(news_res)-2: + break + speak('These were the top headlines, Have a nice day Sir!!..') + + elif 'search google for' in command: + obj.search_anything_google(command) + + elif "play music" in command or "hit some music" in command: + music_dir = "F://Songs//Imagine_Dragons" + songs = os.listdir(music_dir) + for song in songs: + os.startfile(os.path.join(music_dir, song)) + + elif 'youtube' in command: + video = command.split(' ')[1] + speak(f"Okay sir, playing {video} on youtube") + pywhatkit.playonyt(video) + + elif "email" in command or "send email" in command: + sender_email = config.email + sender_password = config.email_password + + try: + speak("Whom do you want to email sir ?") + recipient = obj.mic_input() + receiver_email = EMAIL_DIC.get(recipient) + if receiver_email: + + speak("What is the subject sir ?") + subject = obj.mic_input() + speak("What should I say?") + message = obj.mic_input() + msg = 'Subject: {}\n\n{}'.format(subject, message) + obj.send_mail(sender_email, sender_password, + receiver_email, msg) + speak("Email has been successfully sent") + time.sleep(2) + + else: + speak( + "I coudn't find the requested person's email in my database. Please try again with a different name") + + except: + speak("Sorry sir. Couldn't send your mail. Please try again") + + elif "calculate" in command: + question = command + answer = computational_intelligence(question) + speak(answer) + + elif "what is" in command or "who is" in command: + question = command + answer = computational_intelligence(question) + speak(answer) + + elif "what do i have" in command or "do i have plans" or "am i busy" in command: + obj.google_calendar_events(command) + + if "make a note" in command or "write this down" in command or "remember this" in command: + speak("What would you like me to write down?") + note_text = obj.mic_input() + obj.take_note(note_text) + speak("I've made a note of that") + + elif "close the note" in command or "close notepad" in command: + speak("Okay sir, closing notepad") + os.system("taskkill /f /im notepad++.exe") + + if "joke" in command: + joke = pyjokes.get_joke() + print(joke) + speak(joke) + + elif "system" in command: + sys_info = obj.system_info() + print(sys_info) + speak(sys_info) + + elif "where is" in command: + place = command.split('where is ', 1)[1] + current_loc, target_loc, distance = obj.location(place) + city = target_loc.get('city', '') + state = target_loc.get('state', '') + country = target_loc.get('country', '') + time.sleep(1) + try: + + if city: + res = f"{place} is in {state} state and country {country}. It is {distance} km away from your current location" + print(res) + speak(res) + + else: + res = f"{state} is a state in {country}. It is {distance} km away from your current location" + print(res) + speak(res) + + except: + res = "Sorry sir, I couldn't get the co-ordinates of the location you requested. Please try again" + speak(res) + + elif "ip address" in command: + ip = requests.get('https://api.ipify.org').text + print(ip) + speak(f"Your ip address is {ip}") + + elif "switch the window" in command or "switch window" in command: + speak("Okay sir, Switching the window") + pyautogui.keyDown("alt") + pyautogui.press("tab") + time.sleep(1) + pyautogui.keyUp("alt") + + elif "where i am" in command or "current location" in command or "where am i" in command: + try: + city, state, country = obj.my_location() + print(city, state, country) + speak( + f"You are currently in {city} city which is in {state} state and country {country}") + except Exception as e: + speak( + "Sorry sir, I coundn't fetch your current location. Please try again") + + elif "take screenshot" in command or "take a screenshot" in command or "capture the screen" in command: + speak("By what name do you want to save the screenshot?") + name = obj.mic_input() + speak("Alright sir, taking the screenshot") + img = pyautogui.screenshot() + name = f"{name}.png" + img.save(name) + speak("The screenshot has been succesfully captured") + + elif "show me the screenshot" in command: + try: + img = Image.open('D://JARVIS//JARVIS_2.0//' + name) + img.show(img) + speak("Here it is sir") + time.sleep(2) + + except IOError: + speak("Sorry sir, I am unable to display the screenshot") + + elif "hide all files" in command or "hide this folder" in command: + os.system("attrib +h /s /d") + speak("Sir, all the files in this folder are now hidden") + + elif "visible" in command or "make files visible" in command: + os.system("attrib -h /s /d") + speak("Sir, all the files in this folder are now visible to everyone. I hope you are taking this decision in your own peace") + + # if "calculate" in command or "what is" in command: + # query = command + # answer = computational_intelligence(query) + # speak(answer) + + + + elif "goodbye" in command or "offline" in command or "bye" in command: + speak("Alright sir, going offline. It was nice working with you") + sys.exit() + + +startExecution = MainThread() + + +class Main(QMainWindow): + def __init__(self): + super().__init__() + self.ui = Ui_MainWindow() + self.ui.setupUi(self) + self.ui.pushButton.clicked.connect(self.startTask) + self.ui.pushButton_2.clicked.connect(self.close) + + def __del__(self): + sys.stdout = sys.__stdout__ + + # def run(self): + # self.TaskExection + def startTask(self): + self.ui.movie = QtGui.QMovie("Jarvis/utils/images/live_wallpaper.gif") + self.ui.label.setMovie(self.ui.movie) + self.ui.movie.start() + self.ui.movie = QtGui.QMovie("Jarvis/utils/images/initiating.gif") + self.ui.label_2.setMovie(self.ui.movie) + self.ui.movie.start() + timer = QTimer(self) + timer.timeout.connect(self.showTime) + timer.start(1000) + startExecution.start() + + def showTime(self): + current_time = QTime.currentTime() + current_date = QDate.currentDate() + label_time = current_time.toString('hh:mm:ss') + label_date = current_date.toString(Qt.ISODate) + self.ui.textBrowser.setText(label_date) + self.ui.textBrowser_2.setText(label_time) + + +app = QApplication(sys.argv) +jarvis = Main() +jarvis.show() +exit(app.exec_()) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..f4fd586 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,80 @@ +astroid==2.4.2 +attrs==20.3.0 +autopep8==1.5.4 +beautifulsoup4==4.9.3 +cachetools==4.1.1 +certifi==2020.11.8 +chardet==3.0.4 +click==7.1.2 +colorama==0.4.4 +comtypes==1.1.7 +decorator==4.4.2 +flake8==3.8.4 +future==0.18.2 +geocoder==1.38.1 +geographiclib==1.50 +geopy==2.0.0 +google-api-core==1.23.0 +google-api-python-client==1.12.8 +google-auth==1.23.0 +google-auth-httplib2==0.0.4 +google-auth-oauthlib==0.4.2 +googleapis-common-protos==1.52.0 +httplib2==0.18.1 +hurry==1.1 +hurry.filesize==0.9 +idna==2.10 +isort==5.6.4 +lazy-object-proxy==1.4.3 +lxml==4.6.2 +mccabe==0.6.1 +more-itertools==8.6.0 +MouseInfo==0.1.3 +numpy==1.19.5 +oauthlib==3.1.0 +Pillow==8.0.1 +pprintpp==0.4.0 +protobuf==3.14.0 +psutil==5.7.3 +pyasn1==0.4.8 +pyasn1-modules==0.2.8 +PyAutoGUI==0.9.52 +pycodestyle==2.6.0 +pyflakes==2.2.0 +PyGetWindow==0.0.9 +pyjokes==0.6.0 +pylint==2.6.0 +PyMsgBox==1.0.9 +pyperclip==1.8.1 +pypiwin32==223 +PyQt5==5.15.2 +pyqt5-plugins==5.15.2.2.0.1 +PyQt5-sip==12.8.1 +pyqt5-tools==5.15.2.3 +PyRect==0.1.4 +PyScreeze==0.1.26 +python-dotenv==0.15.0 +pyttsx3==2.90 +PyTweening==1.0.3 +pytz==2020.4 +pywhatkit==3.2 +pywin32==300 +qt5-applications==5.15.2.2.1 +qt5-tools==5.15.2.1.0.1 +ratelim==0.1.6 +requests==2.25.0 +requests-oauthlib==1.3.0 +rsa==4.6 +selenium==3.141.0 +six==1.15.0 +soupsieve==2.0.1 +SpeechRecognition==3.8.1 +toml==0.10.2 +uritemplate==3.0.1 +urllib3==1.26.2 +wikipedia==1.4.0 +wincertstore==0.2 +wolframalpha==4.1.1 +wrapt==1.12.1 +xml-python==0.3.5 +xmltodict==0.12.0 \ No newline at end of file