├── src ├── __init__.py ├── tests │ ├── __init__.py │ ├── test_settings │ │ ├── __init__.py │ │ └── test_settings.py │ └── skill_analyzer_tests.py └── jarvis │ ├── jarvis │ ├── core │ │ ├── __init__.py │ │ ├── nlp.py │ │ ├── processor.py │ │ └── console.py │ ├── skills │ │ ├── __init__.py │ │ ├── collection │ │ │ ├── __init__.py │ │ │ ├── system_health.py │ │ │ ├── text.py │ │ │ ├── libreoffice.py │ │ │ ├── location.py │ │ │ ├── linux.py │ │ │ ├── activation.py │ │ │ ├── wolframalpha.py │ │ │ ├── internet.py │ │ │ ├── remember.py │ │ │ ├── configuration.py │ │ │ ├── math.py │ │ │ ├── history.py │ │ │ ├── datetime.py │ │ │ ├── general.py │ │ │ ├── weather.py │ │ │ ├── info.py │ │ │ ├── reminder.py │ │ │ └── browser.py │ │ ├── skill.py │ │ ├── analyzer.py │ │ └── registry.py │ ├── utils │ │ ├── __init__.py │ │ ├── mapping.py │ │ ├── input.py │ │ ├── console.py │ │ ├── mongoDB.py │ │ └── startup.py │ ├── _version.py │ ├── engines │ │ ├── __init__.py │ │ ├── ttt.py │ │ ├── stt.py │ │ └── tts.py │ ├── files │ │ ├── activation_sound.wav │ │ └── analog_watch_alarm.wav │ ├── enumerations.py │ ├── __init__.py │ └── settings.py │ └── start.py ├── config └── package_file_list.txt ├── .gitattributes ├── imgs ├── Jarvis logo.ai ├── TravisFlow.png ├── jarvis_logo.png ├── Microphone Setup.PNG ├── desicion_model.png ├── Jarvis_printscreen.PNG ├── high-level_design.png └── skill_space_desicion.png ├── .gitignore ├── bin ├── deploy │ ├── create_release_package.sh │ ├── install_python_dependencies.sh │ ├── new_release_auto_tagging.sh │ ├── merge_develop_to_master.sh │ └── install_system_dependencies.sh └── tests │ └── run_tests_on_travis.sh ├── run_jarvis.sh ├── run_tests.sh ├── .github └── FUNDING.yml ├── requirements.txt ├── LICENSE ├── .travis.yml ├── setup.sh └── README.md /src/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/jarvis/jarvis/core/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/jarvis/jarvis/skills/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/jarvis/jarvis/utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/tests/test_settings/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/jarvis/jarvis/skills/collection/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/jarvis/jarvis/_version.py: -------------------------------------------------------------------------------- 1 | __version__ = "1.0.0-beta" -------------------------------------------------------------------------------- /config/package_file_list.txt: -------------------------------------------------------------------------------- 1 | src 2 | requirements.txt 3 | run_jarvis.sh 4 | setup.sh -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | 5 | -------------------------------------------------------------------------------- /imgs/Jarvis logo.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cedev935/Python-AI-App/HEAD/imgs/Jarvis logo.ai -------------------------------------------------------------------------------- /imgs/TravisFlow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cedev935/Python-AI-App/HEAD/imgs/TravisFlow.png -------------------------------------------------------------------------------- /imgs/jarvis_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cedev935/Python-AI-App/HEAD/imgs/jarvis_logo.png -------------------------------------------------------------------------------- /imgs/Microphone Setup.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cedev935/Python-AI-App/HEAD/imgs/Microphone Setup.PNG -------------------------------------------------------------------------------- /imgs/desicion_model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cedev935/Python-AI-App/HEAD/imgs/desicion_model.png -------------------------------------------------------------------------------- /src/jarvis/jarvis/engines/__init__.py: -------------------------------------------------------------------------------- 1 | from .stt import * 2 | from .tts import * 3 | from .ttt import * 4 | -------------------------------------------------------------------------------- /imgs/Jarvis_printscreen.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cedev935/Python-AI-App/HEAD/imgs/Jarvis_printscreen.PNG -------------------------------------------------------------------------------- /imgs/high-level_design.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cedev935/Python-AI-App/HEAD/imgs/high-level_design.png -------------------------------------------------------------------------------- /imgs/skill_space_desicion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cedev935/Python-AI-App/HEAD/imgs/skill_space_desicion.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Exclude python executables and cached folders 2 | *.pyc 3 | __pycache__ 4 | 5 | # Exclude pycharm metadata 6 | .idea/ -------------------------------------------------------------------------------- /bin/deploy/create_release_package.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "Build Jarvis package" 4 | tar -cvf $RELEASE_PACKAGE -T config/package_file_list.txt -------------------------------------------------------------------------------- /src/jarvis/jarvis/files/activation_sound.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cedev935/Python-AI-App/HEAD/src/jarvis/jarvis/files/activation_sound.wav -------------------------------------------------------------------------------- /src/jarvis/jarvis/files/analog_watch_alarm.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cedev935/Python-AI-App/HEAD/src/jarvis/jarvis/files/analog_watch_alarm.wav -------------------------------------------------------------------------------- /bin/deploy/install_python_dependencies.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | #----------------------------------- 4 | # Install Python dependencies 5 | #----------------------------------- 6 | pip install -r requirements.txt 7 | pip install -U scikit-learn -------------------------------------------------------------------------------- /run_jarvis.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # -------------------------------- 3 | # Start MongoDB service 4 | # -------------------------------- 5 | sudo systemctl start mongodb 6 | 7 | # -------------------------------- 8 | # Start Jarvis service with virtualenv 9 | # -------------------------------- 10 | ./jarvis_virtualenv/bin/python ./src/jarvis/start.py 11 | 12 | # -------------------------------- 13 | # Stop MongoDB service 14 | # -------------------------------- 15 | sudo systemctl stop mongodb -------------------------------------------------------------------------------- /bin/deploy/new_release_auto_tagging.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | #-------------------------------------------------- 4 | # Add tag to new release (release based on master) 5 | #------------------------------------------------- 6 | echo "Add tags to new release" 7 | git config --global user.email "builds@travis-ci.com" 8 | git config --global user.name "Travis CI" 9 | export GIT_TAG=Release_v.$TRAVIS_BUILD_NUMBER 10 | git tag $GIT_TAG -a -m "Generated tag from TravisCI for build $TRAVIS_BUILD_NUMBER" 11 | git push -q https://$GITHUB_TOKEN@github.com/ggeop/Python-ai-assistant --tags -------------------------------------------------------------------------------- /bin/deploy/merge_develop_to_master.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$TRAVIS_BRANCH" == "develop" ]; then 4 | git config --global user.email "builds@travis-ci.com" 5 | git config --global user.name "Travis CI" 6 | git remote set-branches --add origin master || echo "Set origin master failed" 7 | git fetch 8 | git reset --hard 9 | git checkout master || echo "Git checkout master failed" 10 | git merge "$TRAVIS_COMMIT" || echo "Merge develop to master failed" 11 | git push -q https://$GITHUB_TOKEN@github.com/ggeop/Python-ai-assistant master 12 | fi 13 | 14 | -------------------------------------------------------------------------------- /run_tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # -------------------------------- 3 | # Activate Python virtual env 4 | # -------------------------------- 5 | export PYTHONPATH="${PYTHONPATH}:./src/jarvis" 6 | source jarvis_virtualenv/bin/activate 7 | 8 | # -------------------------------- 9 | # Start MongoDB service 10 | # -------------------------------- 11 | sudo systemctl start mongodb 12 | 13 | # -------------------------------- 14 | # Run unittests 15 | # -------------------------------- 16 | python -m unittest discover -s ./src -p "*tests.py" 17 | exit_code=($?) 18 | 19 | # -------------------------------- 20 | # Stop MongoDB service 21 | # -------------------------------- 22 | sudo systemctl stop mongodb 23 | 24 | exit $exit_code -------------------------------------------------------------------------------- /bin/tests/run_tests_on_travis.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # -------------------------------- 3 | # Activate Python virtual env 4 | # -------------------------------- 5 | export PYTHONPATH="${PYTHONPATH}:./src/jarvis" 6 | source ~/virtualenv/python3.8/bin/activate 7 | 8 | # -------------------------------- 9 | # Start MongoDB service 10 | # -------------------------------- 11 | sudo systemctl start mongodb 12 | 13 | # -------------------------------- 14 | # Run unittests 15 | # -------------------------------- 16 | python -m unittest discover -s ./src -p "*tests.py" 17 | exit_code=($?) 18 | 19 | # -------------------------------- 20 | # Stop MongoDB service 21 | # -------------------------------- 22 | sudo systemctl stop mongodb 23 | 24 | exit $exit_code -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /bin/deploy/install_system_dependencies.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | #----------------------------------- 4 | # System dependencies installation 5 | #----------------------------------- 6 | sudo apt-get update && / 7 | sudo apt-get install build-essential && / 8 | sudo apt-get install python3-dev && / 9 | sudo apt-get install python3-setuptools && / 10 | sudo apt-get install python3-pip && / 11 | sudo apt-get install python3-venv && / 12 | sudo apt-get install portaudio19-dev python3-pyaudio python3-pyaudio && / 13 | sudo apt-get install libasound2-plugins libsox-fmt-all libsox-dev libxml2-dev libxslt-dev sox ffmpeg && / 14 | sudo apt-get install espeak && / 15 | sudo apt-get install libcairo2-dev libgirepository1.0-dev gir1.2-gtk-3.0 && / 16 | sudo apt install mongodb && / 17 | sudo apt-get install gnupg -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | appdirs==1.4.3 2 | APScheduler==3.6.0 3 | backports.entry-points-selectable==1.1.0 4 | beautifulsoup4==4.7.1 5 | bs4==0.0.1 6 | certifi==2019.3.9 7 | chardet==3.0.4 8 | Click==7.0 9 | colorize==1.1.0 10 | Cython==0.29.24 11 | distlib==0.3.3 12 | filelock==3.3.0 13 | geojson==2.4.1 14 | google==2.0.2 15 | gTTS-token==1.1.3 16 | idna==2.8 17 | inflect==2.1.0 18 | jaraco.itertools==4.4.2 19 | joblib==0.14.0 20 | lxml==4.6.3 21 | mock==3.0.5 22 | more-itertools==7.0.0 23 | nltk==3.4.5 24 | numpy==1.16.4 25 | patch==1.16 26 | platformdirs==2.4.0 27 | playsound==1.2.2 28 | psutil==5.6.6 29 | PTable==0.9.2 30 | PyAudio==0.2.11 31 | pycairo==1.20.1 32 | PyGObject==3.42.0 33 | pymongo==3.10.1 34 | pyowm==2.10.0 35 | pyttsx3==2.71 36 | pytz==2019.1 37 | requests==2.21.0 38 | scipy==1.5.4 39 | six==1.12.0 40 | soupsieve==1.9.1 41 | SpeechRecognition==3.8.1 42 | speedtest-cli==2.1.1 43 | textblob==0.15.3 44 | tzlocal==1.5.1 45 | urllib3==1.24.3 46 | virtualenv==20.8.1 47 | web-cache==1.1.0 48 | wikipedia==1.4.0 49 | wolframalpha==3.0.1 50 | word2number==1.1 51 | xmltodict==0.12.0 52 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Georgios Papachristou 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/jarvis/jarvis/enumerations.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2019 Georgios Papachristou 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | from enum import Enum 24 | 25 | 26 | class InputMode(Enum): 27 | VOICE = 'voice' 28 | TEXT = 'text' 29 | 30 | 31 | class MongoCollections(Enum): 32 | GENERAL_SETTINGS = 'general_settings' 33 | CONTROL_SKILLS = 'control_skills' 34 | ENABLED_BASIC_SKILLS = 'enabled_basic_skills' 35 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | cache: pip 3 | 4 | os: linux 5 | dist: focal 6 | python: 3.8 7 | 8 | jobs: 9 | include: 10 | 11 | - stage: test 12 | before_install: 13 | - sudo bash bin/deploy/install_system_dependencies.sh 14 | install: 15 | - bash bin/deploy/install_python_dependencies.sh 16 | script: 17 | # Run tests by using Travis virtual env (quick way, pip deps are cached) 18 | - sudo bash bin/tests/run_tests_on_travis.sh 19 | 20 | - stage: merge (develop --> master) 21 | install: 22 | - sudo bash setup.sh 23 | script: 24 | # Run tests with the same way as locally 25 | # With this way setup script (setup.sh) is validated 26 | - sudo bash run_tests.sh 27 | after_success: 28 | - sudo bash bin/deploy/merge_develop_to_master.sh 29 | 30 | - stage: deploy 31 | install: skip 32 | script: skip 33 | env: 34 | - RELEASE_PACKAGE=jarvis_package.tar 35 | before_deploy: 36 | - bash bin/deploy/new_release_auto_tagging.sh 37 | - bash bin/deploy/create_release_package.sh 38 | deploy: 39 | provider: releases 40 | skip_cleanup: true 41 | api_key: $GITHUB_TOKEN 42 | file: $RELEASE_PACKAGE 43 | on: 44 | branch: master 45 | 46 | stages: 47 | - name: test 48 | if: NOT (branch IN (develop,master)) AND (NOT(branch==^Release.*)) 49 | - name: merge (develop --> master) 50 | if: branch==develop AND NOT(type==pull_request) 51 | - name: deploy 52 | if: branch==master 53 | 54 | notifications: 55 | email: false 56 | -------------------------------------------------------------------------------- /src/jarvis/jarvis/utils/mapping.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2019 Georgios Papachristou 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | math_symbols_mapping = { 24 | 'equal': '=', 25 | # ---- The following symbols are disable because are not matched correctly with the math skill ----. 26 | # 'open parentheses': '(', 27 | # 'close parentheses': ')', 28 | 'plus': '+', 29 | 'minus': '-', 30 | 'asterisk': '*', 31 | 'divide': '/', 32 | 'modulo': 'mod', 33 | 'power': '**', 34 | 'square root': '**(1/2)' 35 | } 36 | 37 | math_tags = ','.join(list(math_symbols_mapping.keys())) 38 | -------------------------------------------------------------------------------- /src/jarvis/jarvis/skills/collection/system_health.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2019 Georgios Papachristou 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | import os 24 | import psutil 25 | 26 | from jarvis.skills.skill import AssistantSkill 27 | 28 | 29 | class SystemHealthSkills(AssistantSkill): 30 | 31 | @classmethod 32 | def tell_memory_consumption(cls,**kwargs): 33 | """ 34 | Responds the memory consumption of the assistant process. 35 | """ 36 | memory = cls._get_memory_consumption() 37 | cls.response("I use {0:.2f} GB..".format(memory)) 38 | 39 | @classmethod 40 | def _get_memory_consumption(cls): 41 | pid = os.getpid() 42 | py = psutil.Process(pid) 43 | memory_use = py.memory_info()[0] / 2. ** 30 # memory use in GB...I think 44 | return memory_use 45 | -------------------------------------------------------------------------------- /src/jarvis/start.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2019 Georgios Papachristou 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | from jarvis import settings 24 | from jarvis.utils.startup import internet_connectivity_check 25 | from jarvis.core.processor import Processor 26 | from jarvis.core.console import ConsoleManager 27 | 28 | 29 | def main(): 30 | """ 31 | Do initial checks, clear the console and print the assistant logo. 32 | """ 33 | 34 | console_manager = ConsoleManager() 35 | console_manager.console_output(info_log='Wait a second for startup checks..') 36 | internet_connectivity_check() 37 | console_manager.console_output(info_log='Application started') 38 | console_manager.console_output(info_log="I'm ready! Say something :-)") 39 | processor = Processor(console_manager=console_manager, settings_=settings) 40 | 41 | while True: 42 | processor.run() 43 | 44 | 45 | if __name__ == '__main__': 46 | main() 47 | 48 | -------------------------------------------------------------------------------- /src/jarvis/jarvis/skills/collection/text.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2019 Georgios Papachristou 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | import re 24 | import time 25 | 26 | from jarvis.skills.skill import AssistantSkill 27 | 28 | 29 | class WordSkills(AssistantSkill): 30 | 31 | @classmethod 32 | def spell_a_word(cls, voice_transcript, skill, **kwargs): 33 | """ 34 | Spell a words letter by letter. 35 | :param voice_transcript: string (e.g 'spell word animal') 36 | :param skill: dict (e.g 37 | """ 38 | tags = cls.extract_tags(voice_transcript, skill['tags']) 39 | for tag in tags: 40 | reg_ex = re.search(tag + ' ([a-zA-Z]+)', voice_transcript) 41 | try: 42 | if reg_ex: 43 | search_text = reg_ex.group(1) 44 | for letter in search_text: 45 | cls.response(letter) 46 | time.sleep(2) 47 | except Exception as e: 48 | cls.console(error_log=e) 49 | cls.response("I can't spell the word") 50 | -------------------------------------------------------------------------------- /src/jarvis/jarvis/skills/collection/libreoffice.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2019 Georgios Papachristou 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | import subprocess 24 | 25 | from jarvis.skills.skill import AssistantSkill 26 | 27 | 28 | class LibreofficeSkills(AssistantSkill): 29 | 30 | @classmethod 31 | def open_libreoffice_calc(cls, **kwargs): 32 | """ 33 | Opens libreoffice_suite_skills calc application. 34 | """ 35 | cls._open_libreoffice_app('calc') 36 | 37 | @classmethod 38 | def open_libreoffice_impress(cls, **kwargs): 39 | """ 40 | Opens libreoffice_suite_skills impress application. 41 | """ 42 | cls._open_libreoffice_app('impress') 43 | 44 | @classmethod 45 | def open_libreoffice_writer(cls, **kwargs): 46 | """ 47 | Opens libreoffice_suite_skills writer application. 48 | """ 49 | cls._open_libreoffice_app('writer') 50 | 51 | @classmethod 52 | def _open_libreoffice_app(cls, app): 53 | app_arg = '-' + app 54 | subprocess.Popen(['libreoffice', app_arg], stdout=subprocess.PIPE, shell=False) 55 | cls.console('I opened a new' + app + ' document..') 56 | -------------------------------------------------------------------------------- /src/jarvis/jarvis/skills/collection/location.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2019 Georgios Papachristou 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | import requests 24 | import json 25 | import logging 26 | 27 | from jarvis.settings import IPSTACK_API 28 | from jarvis.skills.collection.internet import InternetSkills 29 | from jarvis.skills.skill import AssistantSkill 30 | 31 | 32 | class LocationSkill(AssistantSkill): 33 | 34 | @classmethod 35 | def get_current_location(cls, **kwargs): 36 | location_results = cls.get_location() 37 | if location_results: 38 | city, latitude, longitude = location_results 39 | cls.response("You are in {0}".format(city)) 40 | 41 | @classmethod 42 | def get_location(cls): 43 | try: 44 | send_url = "http://api.ipstack.com/check?access_key=" + IPSTACK_API['key'] 45 | geo_req = requests.get(send_url) 46 | geo_json = json.loads(geo_req.text) 47 | latitude = geo_json['latitude'] 48 | longitude = geo_json['longitude'] 49 | city = geo_json['city'] 50 | return city, latitude, longitude 51 | except Exception as e: 52 | if InternetSkills.internet_availability(): 53 | # If there is an error but the internet connect is good, then the location API has problem 54 | cls.console(error_log=e) 55 | logging.debug("Unable to get current location with error message: {0}".format(e)) 56 | return None 57 | -------------------------------------------------------------------------------- /src/jarvis/jarvis/engines/ttt.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2019 Georgios Papachristou 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | import logging 24 | from jarvis.core.console import ConsoleManager 25 | from jarvis.utils.console import user_input 26 | 27 | 28 | class TTTEngine: 29 | """ 30 | Text To Text Engine (TTT) 31 | """ 32 | def __init__(self): 33 | self.logger = logging 34 | self.console_manager = ConsoleManager() 35 | 36 | def recognize_input(self, **kwargs): 37 | """ 38 | Recognize input from console and returns transcript if its not empty string. 39 | """ 40 | try: 41 | text_transcript = input(user_input).lower() 42 | while text_transcript == '': 43 | text_transcript = input(user_input).lower() 44 | return text_transcript 45 | except EOFError as e: 46 | self.console_manager.console_output(error_log='Failed to recognize user input with message: {0}'.format(e)) 47 | 48 | def assistant_response(self, message, refresh_console=True): 49 | """ 50 | Assistant response in voice or/and in text. 51 | :param refresh_console: boolean 52 | :param message: string 53 | """ 54 | try: 55 | if message: 56 | self.console_manager.console_output(message, refresh_console=refresh_console) 57 | except RuntimeError as e: 58 | self.console_manager.console_output(error_log='Error in assistant response with message: {0}'.format(e)) 59 | -------------------------------------------------------------------------------- /src/jarvis/jarvis/skills/collection/linux.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2019 Georgios Papachristou 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | import subprocess 24 | import logging 25 | import time 26 | 27 | from jarvis.skills.skill import AssistantSkill 28 | 29 | 30 | class LinuxAppSkills(AssistantSkill): 31 | 32 | @classmethod 33 | def open_new_bash(cls, **kwargs): 34 | """ 35 | Opens new bash terminal. 36 | """ 37 | try: 38 | subprocess.Popen(['gnome-terminal'], stderr=subprocess.PIPE, shell=False).communicate() 39 | except Exception as e: 40 | cls.response("An error occurred, I can't open new bash terminal") 41 | logging.debug(e) 42 | 43 | @classmethod 44 | def open_note_app(cls, **kwargs): 45 | """ 46 | Opens a note editor (gedit). 47 | """ 48 | try: 49 | subprocess.Popen(['gedit'], stderr=subprocess.PIPE, shell=False).communicate() 50 | except FileNotFoundError: 51 | cls.response("You don't have installed the gedit") 52 | time.sleep(2) 53 | cls.response("Install gedit with the following command: 'sudo apt-get install gedit'") 54 | 55 | @classmethod 56 | def open_new_browser_window(cls, **kwargs): 57 | """ 58 | Opens new browser window. 59 | """ 60 | try: 61 | subprocess.Popen(['firefox'], stderr=subprocess.PIPE, shell=False).communicate() 62 | except Exception as e: 63 | cls.response("An error occurred, I can't open firefox") 64 | logging.debug(e) -------------------------------------------------------------------------------- /src/jarvis/jarvis/skills/collection/activation.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2019 Georgios Papachristou 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | import sys 24 | import time 25 | from datetime import datetime 26 | 27 | from jarvis.skills.skill import AssistantSkill 28 | from jarvis.utils.startup import play_activation_sound 29 | from jarvis.utils.mongoDB import db 30 | from jarvis.enumerations import InputMode, MongoCollections 31 | 32 | 33 | class ActivationSkills(AssistantSkill): 34 | 35 | @classmethod 36 | def enable_assistant(cls, **kwargs): 37 | """ 38 | Plays activation sound and creates the assistant response according to the day hour. 39 | """ 40 | 41 | input_mode = db.get_documents(collection=MongoCollections.GENERAL_SETTINGS.value)[0]['input_mode'] 42 | if input_mode == InputMode.VOICE.value: 43 | play_activation_sound() 44 | 45 | @classmethod 46 | def disable_assistant(cls, **kwargs): 47 | """ 48 | - Clear console 49 | - Shutdown the assistant service 50 | """ 51 | 52 | cls.response('Bye') 53 | time.sleep(1) 54 | cls.console(info_log='Application terminated gracefully.') 55 | sys.exit() 56 | 57 | @classmethod 58 | def assistant_greeting(cls, **kwargs): 59 | """ 60 | Assistant greeting based on day hour. 61 | """ 62 | now = datetime.now() 63 | day_time = int(now.strftime('%H')) 64 | 65 | if day_time < 12: 66 | cls.response('Good morning sir') 67 | elif 12 <= day_time < 18: 68 | cls.response('Good afternoon sir') 69 | else: 70 | cls.response('Good evening sir') 71 | -------------------------------------------------------------------------------- /src/jarvis/jarvis/skills/collection/wolframalpha.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2019 Georgios Papachristou 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | import wolframalpha 24 | 25 | from jarvis.settings import WOLFRAMALPHA_API 26 | from jarvis.skills.collection.internet import InternetSkills 27 | from jarvis.skills.skill import AssistantSkill 28 | 29 | 30 | class WolframSkills(AssistantSkill): 31 | 32 | @classmethod 33 | def call_wolframalpha(cls, voice_transcript, **kwargs): 34 | """ 35 | Make a request in wolfram Alpha API and prints the response. 36 | """ 37 | client = wolframalpha.Client(WOLFRAMALPHA_API['key']) 38 | if voice_transcript: 39 | try: 40 | if WOLFRAMALPHA_API['key']: 41 | cls.console(info_log='Wolfarm APi call with query message: {0}'.format(voice_transcript)) 42 | cls.response("Wait a second, I search..") 43 | res = client.query(voice_transcript) 44 | wolfram_result = next(res.results).text 45 | cls.console(debug_log='Successful response from Wolframalpha') 46 | cls.response(wolfram_result) 47 | return wolfram_result 48 | else: 49 | cls.response("WolframAlpha API is not working.\n" 50 | "You can get an API key from: https://developer.wolframalpha.com/portal/myapps/ ") 51 | except Exception as e: 52 | if InternetSkills.internet_availability(): 53 | # If there is an error but the internet connect is good, then the Wolfram API has problem 54 | cls.console(error_log='There is no result from Wolfram API with error: {0}'.format(e)) 55 | else: 56 | cls.response('Sorry, but I could not find something') 57 | -------------------------------------------------------------------------------- /src/jarvis/jarvis/skills/collection/internet.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2019 Georgios Papachristou 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | import requests 24 | import logging 25 | import speedtest 26 | 27 | from jarvis.skills.skill import AssistantSkill 28 | from jarvis.utils.startup import internet_connectivity_check 29 | 30 | 31 | class InternetSkills(AssistantSkill): 32 | 33 | @classmethod 34 | def run_speedtest(cls, **kwargs): 35 | """ 36 | Run an internet speed test. Speed test will show 37 | 1) Download Speed 38 | 2) Upload Speed 39 | 3) Ping 40 | """ 41 | try: 42 | cls.response("Sure! wait a second to measure") 43 | st = speedtest.Speedtest() 44 | server_names = [] 45 | st.get_servers(server_names) 46 | 47 | downlink_bps = st.download() 48 | uplink_bps = st.upload() 49 | ping = st.results.ping 50 | up_mbps = uplink_bps / 1000000 51 | down_mbps = downlink_bps / 1000000 52 | 53 | cls.response("Speedtest results:\n" 54 | "The ping is: %s ms \n" 55 | "The upling is: %0.2f Mbps \n" 56 | "The downling is: %0.2f Mbps" % (ping, up_mbps, down_mbps) 57 | ) 58 | 59 | except Exception as e: 60 | cls.response("I coudn't run a speedtest") 61 | logging.error("Speedtest error with message: {0}".format(e)) 62 | 63 | @classmethod 64 | def internet_availability(cls, **kwargs): 65 | """ 66 | Tells to the user is the internet is available or not. 67 | """ 68 | if internet_connectivity_check(): 69 | cls.response("The internet connection is ok") 70 | return True 71 | else: 72 | cls.response("The internet is down for now") 73 | return False 74 | -------------------------------------------------------------------------------- /src/jarvis/jarvis/utils/input.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2019 Georgios Papachristou 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | import jarvis 24 | from jarvis.core.console import ConsoleManager 25 | 26 | console_manager = ConsoleManager() 27 | 28 | 29 | def validate_digits_input(message, values_range=None): 30 | """ 31 | Checks users input to be only numbers else it will be in infinite loop for a right value. 32 | Extra parameter 'values_range' checks the input to be between a range. 33 | """ 34 | input_number = None 35 | while True: 36 | jarvis.output_engine.assistant_response(message) 37 | user_input = jarvis.input_engine.recognize_input(already_activated=True) 38 | try: 39 | input_number = int(user_input) 40 | except ValueError: 41 | continue 42 | 43 | if values_range: 44 | min_value = values_range[0] 45 | max_value = values_range[1] 46 | if not min_value <= input_number <= max_value: 47 | jarvis.output_engine.assistant_response\ 48 | ("Please give a number higher/equal than {0} and smaller/equal than {1}".format(min_value, max_value)) 49 | raise ValueError 50 | else: 51 | break 52 | return input_number 53 | 54 | 55 | def check_input_to_continue(message=''): 56 | positive_answers = ['yes', 'y', 'sure', 'yeah'] 57 | if message: 58 | console_manager.console_output(message + ' (y/n): ', refresh_console=False) 59 | return jarvis.input_engine.recognize_input(already_activated=True) in positive_answers 60 | 61 | 62 | def validate_input_with_choices(available_choices): 63 | user_input = jarvis.input_engine.recognize_input(already_activated=True) 64 | while not user_input in available_choices: 65 | jarvis.output_engine.assistant_response('Please select on of the values: {0}'.format(available_choices)) 66 | user_input = jarvis.input_engine.recognize_input(already_activated=True) 67 | return user_input 68 | 69 | -------------------------------------------------------------------------------- /src/jarvis/jarvis/utils/console.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2019 Georgios Papachristou 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | import os 24 | 25 | from jarvis._version import __version__ 26 | 27 | 28 | class OutputStyler: 29 | HEADER = '\033[95m' 30 | BLUE = '\033[94m' 31 | GREEN = '\033[92m' 32 | CYAN = '\033[36m' 33 | WARNING = '\033[93m' 34 | FAIL = '\033[91m' 35 | ENDC = '\033[0m' 36 | BOLD = '\033[1m' 37 | UNDERLINE = '\033[4m' 38 | 39 | 40 | user_input = OutputStyler.CYAN + ':-$ ' + OutputStyler.ENDC 41 | 42 | DASH = '=' 43 | 44 | 45 | def headerize(text=DASH): 46 | """ 47 | Add dashes based on terminal length. 48 | 49 | Example: 50 | --------------------------------------------------- 51 | text --> Result 52 | --------------------------------------------------- 53 | 54 | SYSTEM --> ================ SYSTEM ================ 55 | None --> ======================================== 56 | 57 | """ 58 | 59 | process = os.popen('stty size', 'r') 60 | result = process.read() 61 | process.close() 62 | terminal_height, terminal_length = result.split() 63 | if text: 64 | text_length = len(text) 65 | remaining_places = int(terminal_length) - text_length 66 | if remaining_places > 0: 67 | return DASH * (remaining_places // 2 - 1) + ' ' + text + ' ' + DASH * (remaining_places // 2 - 1) 68 | else: 69 | # If there is no text, it returns a line with the length of terminal. 70 | return DASH * int(terminal_length) 71 | 72 | 73 | def print_console_header(text=DASH): 74 | """ 75 | Create a dynamic header based on terminal length. 76 | """ 77 | print(headerize(text)) 78 | 79 | 80 | jarvis_logo = "\n" \ 81 | " ██╗ █████╗ ██████╗ ██╗ ██╗██╗███████╗\n" \ 82 | " ██║██╔══██╗██╔══██╗██║ ██║██║██╔════╝\n" \ 83 | " ██║███████║██████╔╝██║ ██║██║███████╗\n" \ 84 | " ██ ██║██╔══██║██╔══██╗╚██╗ ██╔╝██║╚════██║\n" \ 85 | " ╚█████╔╝██║ ██║██║ ██║ ╚████╔╝ ██║███████║\n" \ 86 | " ╚════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═══╝ ╚═╝╚══════╝\n" 87 | start_text =" - Voice Assistant Platform " + "v" + __version__ + " -" 88 | -------------------------------------------------------------------------------- /src/jarvis/jarvis/skills/collection/remember.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2019 Georgios Papachristou 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | 24 | from jarvis.skills.skill import AssistantSkill 25 | from jarvis.utils.mongoDB import db 26 | from jarvis.utils import input 27 | 28 | header = """ 29 | ----------------------------------------------------------------------------------------------- 30 | I would like to learn, tell me the right answer! 31 | ----------------------------------------------------------------------------------------------- 32 | * Note: Create new skill! Write your question and the appropriate answer. 33 | \n 34 | """ 35 | 36 | 37 | class RememberSkills(AssistantSkill): 38 | 39 | @classmethod 40 | def remember(cls, **kwargs): 41 | cls.console(header) 42 | continue_add = True 43 | while continue_add: 44 | cls.console(text='Question: ') 45 | tags = cls.user_input() 46 | cls.console(text='Suggested Response: ') 47 | response = cls.user_input() 48 | new_skill = {'name': 'learned_skill', 49 | 'enable': True, 50 | 'func': cls.tell_response.__name__, 51 | 'response': response, 52 | 'tags': tags, 53 | }, 54 | 55 | cls.response('Add more? ', refresh_console=False) 56 | continue_add = input.check_input_to_continue() 57 | db.insert_many_documents(collection='learned_skills', documents=new_skill) 58 | 59 | @classmethod 60 | def tell_response(cls, **kwargs): 61 | cls.response(kwargs.get('skill').get('response')) 62 | 63 | @classmethod 64 | def clear_learned_skills(cls, **kwargs): 65 | if db.is_collection_empty(collection='learned_skills'): 66 | cls.response("I can't find learned skills in my database") 67 | else: 68 | cls.response('I found learned skills..') 69 | cls.response('Are you sure to remove learned skills? ', refresh_console=False) 70 | user_answer = input.check_input_to_continue() 71 | if user_answer: 72 | db.drop_collection(collection='learned_skills') 73 | cls.response("Perfect I have deleted them all") 74 | -------------------------------------------------------------------------------- /src/jarvis/jarvis/utils/mongoDB.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2019 Georgios Papachristou 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | import logging 24 | from pymongo import MongoClient, DESCENDING 25 | 26 | 27 | class MongoDB: 28 | """ 29 | This class encapsulates methods related to MongoDB 30 | """ 31 | 32 | def __init__(self, host='localhost', port=27017): 33 | self.client = MongoClient(host, port) 34 | self.database = self.client['jarvis'] 35 | 36 | def get_documents(self, collection, key=None, limit=None): 37 | collection_obj = self.database[collection] 38 | try: 39 | result = collection_obj.find(key).sort('_id', DESCENDING) 40 | return list(result.limit(limit) if limit else result) 41 | except Exception as e: 42 | logging.error(e) 43 | 44 | def insert_many_documents(self, collection, documents): 45 | collection_obj = self.database[collection] 46 | try: 47 | collection_obj.insert_many(documents) 48 | except Exception as e: 49 | logging.error(e) 50 | 51 | def drop_collection(self, collection): 52 | collection_obj = self.database[collection] 53 | try: 54 | collection_obj.drop() 55 | except Exception as e: 56 | logging.error(e) 57 | 58 | def update_collection(self, collection, documents): 59 | self.drop_collection(collection) 60 | self.insert_many_documents(collection, documents) 61 | 62 | def update_document(self, collection, query, new_value, upsert=True): 63 | collection_obj = self.database[collection] 64 | try: 65 | collection_obj.update_one(query, {'$set': new_value}, upsert) 66 | except Exception as e: 67 | logging.error(e) 68 | 69 | def is_collection_empty(self, collection): 70 | collection_obj = self.database[collection] 71 | try: 72 | return collection_obj.estimated_document_count() == 0 73 | except Exception as e: 74 | logging.error(e) 75 | 76 | 77 | # ---------------------------------------------------------------------------------------------------------------------- 78 | # Create MongoDB connection instance 79 | # ---------------------------------------------------------------------------------------------------------------------- 80 | db = MongoDB() 81 | -------------------------------------------------------------------------------- /src/jarvis/jarvis/__init__.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2019 Georgios Papachristou 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | from logging import config 24 | 25 | from jarvis import settings 26 | from jarvis.settings import ROOT_LOG_CONF 27 | from jarvis.utils.mongoDB import db 28 | from jarvis.utils.startup import configure_MongoDB 29 | from jarvis.enumerations import InputMode 30 | import jarvis.engines as engines 31 | 32 | # ---------------------------------------------------------------------------------------------------------------------- 33 | # Create a Console & Rotating file logger 34 | # ---------------------------------------------------------------------------------------------------------------------- 35 | config.dictConfig(ROOT_LOG_CONF) 36 | 37 | # ---------------------------------------------------------------------------------------------------------------------- 38 | # Clear log file in each assistant fresh start 39 | # ---------------------------------------------------------------------------------------------------------------------- 40 | with open(ROOT_LOG_CONF['handlers']['file']['filename'], 'w') as f: 41 | f.close() 42 | 43 | # ---------------------------------------------------------------------------------------------------------------------- 44 | # Configuare MongoDB, load skills and settings 45 | # ---------------------------------------------------------------------------------------------------------------------- 46 | configure_MongoDB(db, settings) 47 | 48 | # ---------------------------------------------------------------------------------------------------------------------- 49 | # Get assistant settings 50 | # ---------------------------------------------------------------------------------------------------------------------- 51 | input_mode = db.get_documents(collection='general_settings')[0]['input_mode'] 52 | response_in_speech = db.get_documents(collection='general_settings')[0]['response_in_speech'] 53 | assistant_name = db.get_documents(collection='general_settings')[0]['assistant_name'] 54 | 55 | # ---------------------------------------------------------------------------------------------------------------------- 56 | # Create assistant input and output engine instances 57 | # ---------------------------------------------------------------------------------------------------------------------- 58 | input_engine = engines.STTEngine() if input_mode == InputMode.VOICE.value else engines.TTTEngine() 59 | output_engine = engines.TTSEngine() if response_in_speech else engines.TTTEngine() 60 | -------------------------------------------------------------------------------- /src/jarvis/jarvis/skills/skill.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2019 Georgios Papachristou 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | from jarvis.core.console import ConsoleManager 24 | import jarvis 25 | 26 | 27 | class AssistantSkill: 28 | """ 29 | This class is the parent of all skill classes. 30 | """ 31 | 32 | first_activation = True 33 | console_manager = ConsoleManager() 34 | 35 | @classmethod 36 | def console(cls, text='', debug_log=None, info_log=None, warn_log=None, error_log=None, refresh_console=True): 37 | """ 38 | Prints the text only in console and update the console info. 39 | """ 40 | 41 | cls.console_manager.console_output(text=text, 42 | debug_log=debug_log, 43 | info_log=info_log, 44 | warn_log=warn_log, 45 | error_log=error_log, 46 | refresh_console=refresh_console) 47 | 48 | @classmethod 49 | def response(cls, text, refresh_console=True): 50 | """ 51 | The mode of the response depends on the output engine: 52 | - TTT Engine: The response is only in text 53 | - TTS Engine: The response is in voice and text 54 | """ 55 | jarvis.output_engine.assistant_response(text, refresh_console=refresh_console) 56 | 57 | @classmethod 58 | def user_input(cls): 59 | user_input = jarvis.input_engine.recognize_input(already_activated=True) 60 | return user_input 61 | 62 | @classmethod 63 | def extract_tags(cls, voice_transcript, tags): 64 | """ 65 | This method identifies the tags from the user transcript for a specific skill. 66 | 67 | e.x 68 | Let's that the user says "hi jarvis!". 69 | The skill analyzer will match it with enable_assistant skill which has tags 'hi, hello ..' 70 | This method will identify the that the enabled word was the 'hi' not the hello. 71 | 72 | :param voice_transcript: string 73 | :param tags: string 74 | :return: set 75 | """ 76 | 77 | try: 78 | transcript_words = voice_transcript.split() 79 | tags = tags.split(',') 80 | return set(transcript_words).intersection(tags) 81 | except Exception as e: 82 | cls.console_manager.console_output(info_log='Failed to extract tags with message: {0}'.format(e)) 83 | return set() 84 | -------------------------------------------------------------------------------- /src/jarvis/jarvis/skills/collection/configuration.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2019 Georgios Papachristou 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | import importlib 23 | 24 | 25 | from jarvis.skills.skill import AssistantSkill 26 | from jarvis import settings 27 | from jarvis.utils.mongoDB import db 28 | from jarvis.enumerations import InputMode, MongoCollections 29 | from jarvis.utils import console 30 | from jarvis.utils import input 31 | 32 | input_mode = db.get_documents(collection='general_settings')[0]['input_mode'] 33 | response_in_speech = db.get_documents(collection='general_settings')[0]['response_in_speech'] 34 | assistant_name = db.get_documents(collection='general_settings')[0]['assistant_name'] 35 | 36 | 37 | class ConfigurationSkills(AssistantSkill): 38 | 39 | @classmethod 40 | def configure_assistant(cls, **kwargs): 41 | 42 | console.print_console_header('Configure assistant') 43 | cls.console('NOTE: Current name: {0}'.format(assistant_name), refresh_console=False) 44 | console.headerize() 45 | cls.response('Set new assistant name: ', refresh_console=False) 46 | new_assistant_name = cls.user_input() 47 | 48 | console.headerize() 49 | 50 | cls.console('NOTE: Current mode: {0}'.format(input_mode), refresh_console=False) 51 | console.headerize() 52 | cls.response('Set new input mode (text or voice): ') 53 | input_mode_values = [mode.value for mode in InputMode] 54 | new_input_mode = input.validate_input_with_choices(available_choices=input_mode_values) 55 | 56 | console.headerize() 57 | cls.response('Do you want response in speech?', refresh_console=False) 58 | new_response_in_speech = input.check_input_to_continue() 59 | 60 | new_settings = { 61 | 'assistant_name': new_assistant_name, 62 | 'input_mode': new_input_mode, 63 | 'response_in_speech': new_response_in_speech, 64 | } 65 | 66 | cls.console("\n The new settings are the following: \n", refresh_console=False) 67 | for setting_desc, value in new_settings.items(): 68 | cls.console('* {0}: {1}'.format(setting_desc, value), refresh_console=False) 69 | 70 | cls.response('Do you want to save new settings? ', refresh_console=False) 71 | save = input.check_input_to_continue() 72 | if save: 73 | db.update_collection(collection=MongoCollections.GENERAL_SETTINGS.value, documents=[new_settings]) 74 | 75 | import jarvis 76 | importlib.reload(jarvis) 77 | 78 | -------------------------------------------------------------------------------- /src/jarvis/jarvis/skills/collection/math.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2019 Georgios Papachristou 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | from word2number import w2n 24 | 25 | from jarvis.skills.skill import AssistantSkill 26 | from jarvis.utils.mapping import math_symbols_mapping 27 | 28 | 29 | class MathSkills(AssistantSkill): 30 | 31 | @classmethod 32 | def do_calculations(cls, voice_transcript, **kwargs): 33 | """ 34 | Do basic operations with numbers in digits or in number words 35 | 36 | e.x 37 | - one plus one = 2 38 | - 1 + 1 = 2 39 | - one plus 1 = 2 40 | - one + one = 2 41 | 42 | # ------------------------------------------------ 43 | # Current Limitations 44 | # ------------------------------------------------ 45 | * Only basic operators are supported 46 | * In the text need spaces to understand the input e.g 3+4 it's not working, but 3 + 4 works! 47 | 48 | """ 49 | transcript_with_numbers = cls._replace_words_with_numbers(voice_transcript) 50 | math_equation = cls._clear_transcript(transcript_with_numbers) 51 | try: 52 | result = str(eval(math_equation)) 53 | cls.response(result) 54 | except Exception as e: 55 | cls.console_manager.console_output('Failed to eval the equation --> {0} with error message {1}'.format(math_equation, e)) 56 | 57 | @classmethod 58 | def _replace_words_with_numbers(cls, transcript): 59 | transcript_with_numbers = '' 60 | for word in transcript.split(): 61 | try: 62 | number = w2n.word_to_num(word) 63 | transcript_with_numbers += ' ' + str(number) 64 | except ValueError as e: 65 | # If word is not a number words it has 'ValueError' 66 | # In this case we add the word as it is 67 | transcript_with_numbers += ' ' + word 68 | return transcript_with_numbers 69 | 70 | @classmethod 71 | def _clear_transcript(cls, transcript): 72 | """ 73 | Keep in transcript only numbers and operators 74 | """ 75 | cleaned_transcript = '' 76 | for word in transcript.split(): 77 | if word.isdigit() or word in math_symbols_mapping.values(): 78 | # Add numbers 79 | cleaned_transcript += word 80 | else: 81 | # Add operators 82 | cleaned_transcript += math_symbols_mapping.get(word, '') 83 | return cleaned_transcript 84 | -------------------------------------------------------------------------------- /src/jarvis/jarvis/skills/collection/history.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2019 Georgios Papachristou 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | import re 24 | 25 | from jarvis.skills.skill import AssistantSkill 26 | from jarvis.utils.mongoDB import db 27 | 28 | header = """ 29 | ----------------------------------------------------------------------------------------------- 30 | - History - 31 | ----------------------------------------------------------------------------------------------- 32 | * Note: The default limit is 3. Change the limit by adding a number e.g show me user history 10. 33 | 34 | """ 35 | 36 | response_base = """ 37 | * User Transcript: {0} 38 | * Response: {1} 39 | * Executed Skill: {2} 40 | -----------------------------------------------------------------------------------------------""" 41 | 42 | 43 | class HistorySkills(AssistantSkill): 44 | default_limit = 3 45 | 46 | @classmethod 47 | def show_history_log(cls, voice_transcript, skill): 48 | """ 49 | This method cls.consoles user commands history & assistant responses. 50 | 51 | """ 52 | 53 | limit = cls._extract_history_limit(voice_transcript, skill) 54 | limit = limit if limit else cls.default_limit 55 | documents = db.get_documents(collection='history', limit=limit) 56 | response = cls._create_response(documents) 57 | cls.console(response) 58 | 59 | @classmethod 60 | def _create_response(cls, documents): 61 | response = '' 62 | try: 63 | for document in documents: 64 | response += response_base.format(document.get('user_transcript', '--'), 65 | document.get('response', '--'), 66 | document.get('executed_skill').get('skill').get('name') if 67 | document.get('executed_skill') else '--' 68 | ) 69 | except Exception as e: 70 | cls.console(error_log=e) 71 | finally: 72 | from jarvis.utils import input, console 73 | return header + response 74 | 75 | @classmethod 76 | def _extract_history_limit(cls, voice_transcript, skill): 77 | tags = cls.extract_tags(voice_transcript, skill['tags']) 78 | only_number_regex = '([0-9]+$)' 79 | for tag in tags: 80 | reg_ex = re.search(tag + ' ' + only_number_regex, voice_transcript) 81 | if reg_ex: 82 | limit = int(reg_ex.group(1)) 83 | return limit 84 | -------------------------------------------------------------------------------- /src/tests/test_settings/test_settings.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2019 Georgios Papachristou 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | from jarvis.settings import * 24 | 25 | ROOT_LOG_CONF = { 26 | 'version': 1, 27 | 'root': { 28 | 'level': 'DEBUG', 29 | 'handlers': ['file'], 30 | }, 31 | 'handlers': { 32 | 'file': { 33 | 'class': 'logging.handlers.RotatingFileHandler', 34 | 'level': 'DEBUG', 35 | 'formatter': 'detailed', 36 | 'filename': '/var/log/jarvis.log', 37 | 'mode': 'a', 38 | 'maxBytes': 10000000, 39 | 'backupCount': 3, 40 | }, 41 | }, 42 | 'formatters': { 43 | 'detailed': { 44 | 'format': '%(asctime)s - %(name)s - %(levelname)s - %(message)s' 45 | }, 46 | } 47 | } 48 | 49 | # General assistant settings 50 | GENERAL_SETTINGS = { 51 | 'assistant_name': 'Jarvis', 52 | 'enabled_period': 300, # In seconds 53 | 'commands_type': 'text', # voice: The assistant waiting for voice commands, 54 | # text: The assistant waiting for text commands 55 | 'response_in_speech': True, 56 | } 57 | 58 | # Google API Speech recognition settings 59 | # SpeechRecognition: https://pypi.org/project/SpeechRecognition/2.1.3 60 | SPEECH_RECOGNITION = { 61 | 'ambient_duration': 1, # Time for auto microphone calibration 62 | 'pause_threshold': 1, # minimum length silence (in seconds) at the end of a sentence 63 | 'energy_threshold': 3000, # microphone sensitivity, for loud places, the energy level should be up to 4000 64 | 'dynamic_energy_threshold': True # For unpredictable noise levels (Suggested to be TRUE) 65 | } 66 | 67 | # SKill analyzer settings 68 | ANALYZER = { 69 | # SKill analyzer (TfidfVectorizer args) 70 | 'args': { 71 | "stop_words": None, 72 | "lowercase": True, 73 | "norm": 'l1', 74 | "use_idf": False, 75 | }, 76 | 'sensitivity': 0.2, 77 | 78 | } 79 | 80 | # Google text to speech API settings 81 | GOOGLE_SPEECH = { 82 | 'lang': "en" 83 | } 84 | 85 | 86 | # Open weather map API settings 87 | # Create key: https://openweathermap.org/appid 88 | WEATHER_API = { 89 | 'unit': 'celsius', 90 | 'key': None 91 | } 92 | 93 | # WolframAlpha API settings 94 | # Create key: https://developer.wolframalpha.com/portal/myapps/ 95 | WOLFRAMALPHA_API = { 96 | 'key': None 97 | } 98 | 99 | # IPSTACK API settings 100 | #Create key: https://ipstack.com/signup/free 101 | IPSTACK_API = { 102 | 'key': None 103 | } -------------------------------------------------------------------------------- /src/jarvis/jarvis/skills/analyzer.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2019 Georgios Papachristou 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | import logging 23 | from jarvis.utils.mapping import math_symbols_mapping 24 | from jarvis.utils.mongoDB import db 25 | 26 | 27 | class SkillAnalyzer: 28 | def __init__(self, weight_measure, similarity_measure, args, sensitivity): 29 | self.logger = logging 30 | self.weight_measure = weight_measure 31 | self.similarity_measure = similarity_measure 32 | self.args = args 33 | self.vectorizer = self._create_vectorizer() 34 | self.analyzer_sensitivity = sensitivity 35 | 36 | @property 37 | def skills(self): 38 | return db.get_documents(collection='control_skills')\ 39 | + db.get_documents(collection='enabled_basic_skills')\ 40 | + db.get_documents(collection='learned_skills') 41 | 42 | @property 43 | def tags(self): 44 | tags_list = [] 45 | for skill in self.skills: 46 | tags_list.append(skill['tags'].split(',')) 47 | return [','.join(tag) for tag in tags_list] 48 | 49 | def extract(self, user_transcript): 50 | 51 | train_tdm = self._train_model() 52 | user_transcript_with_replaced_math_symbols = self._replace_math_symbols_with_words(user_transcript) 53 | 54 | test_tdm = self.vectorizer.transform([user_transcript_with_replaced_math_symbols]) 55 | 56 | similarities = self.similarity_measure(train_tdm, test_tdm) # Calculate similarities 57 | 58 | skill_index = similarities.argsort(axis=None)[-1] # Extract the most similar skill 59 | if similarities[skill_index] > self.analyzer_sensitivity: 60 | skill_key = [skill for skill in enumerate(self.skills) if skill[0] == skill_index][0][1] 61 | return skill_key 62 | else: 63 | self.logger.debug('Not extracted skills from user voice transcript') 64 | return None 65 | 66 | def _replace_math_symbols_with_words(self, transcript): 67 | replaced_transcript = '' 68 | for word in transcript.split(): 69 | if word in math_symbols_mapping.values(): 70 | for key, value in math_symbols_mapping.items(): 71 | if value == word: 72 | replaced_transcript += ' ' + key 73 | else: 74 | replaced_transcript += ' ' + word 75 | return replaced_transcript 76 | 77 | def _create_vectorizer(self): 78 | """ 79 | Create vectorizer. 80 | """ 81 | return self.weight_measure(**self.args) 82 | 83 | def _train_model(self): 84 | """ 85 | Create/train the model. 86 | """ 87 | return self.vectorizer.fit_transform(self.tags) 88 | -------------------------------------------------------------------------------- /setup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # ----------------------------------- 4 | # Initialization 5 | # ----------------------------------- 6 | JARVIS_DIR=$(pwd) 7 | VIRTUAL_ENV="jarvis_virtualenv" 8 | 9 | green=`tput setaf 2` 10 | red=`tput setaf 1` 11 | reset=`tput sgr0` 12 | 13 | # ----------------------------------- 14 | # Python version compatibility check 15 | # ----------------------------------- 16 | version=$(python3 -V 2>&1 | grep -Po '(?<=Python )(.+)') 17 | if [[ -z "$version" ]] 18 | then 19 | echo "${red} No Python 3.x.x in your system${reset}" 20 | exit 1 21 | else 22 | echo "${green} System Python version is: Python ${version} ${reset}" 23 | fi 24 | 25 | #----------------------------------- 26 | # System dependencies installation 27 | #----------------------------------- 28 | sudo apt-get update && / 29 | sudo apt-get install build-essential && / 30 | sudo apt-get install python3-dev && / 31 | sudo apt-get install python3-setuptools && / 32 | sudo apt-get install python3-pip && / 33 | sudo apt-get install python3-venv && / 34 | sudo apt-get install portaudio19-dev python3-pyaudio python3-pyaudio && / 35 | sudo apt-get install libasound2-plugins libsox-fmt-all libsox-dev libxml2-dev libxslt-dev sox ffmpeg && / 36 | sudo apt-get install espeak && / 37 | sudo apt-get install libcairo2-dev libgirepository1.0-dev gir1.2-gtk-3.0 && / 38 | sudo apt install mongodb && / 39 | sudo apt-get install gnupg 40 | 41 | # Reload local package database 42 | sudo apt-get update 43 | 44 | RESULT=$? 45 | if [ $RESULT -eq 0 ]; then 46 | echo "${green} System dependencies installation succeeded! ${reset}" 47 | else 48 | echo "${red} System dependencies installation failed ${reset}" 49 | exit 1 50 | fi 51 | 52 | #----------------------------------- 53 | # Create Jarvis virtual env 54 | #----------------------------------- 55 | python3 -m venv $JARVIS_DIR/$VIRTUAL_ENV 56 | 57 | RESULT=$? 58 | if [ $RESULT -eq 0 ]; then 59 | echo "${green} Jarvis virtual env creation succeeded! ${reset}" 60 | else 61 | echo "${red} Jarvis virtual env creation failed ${reset}" 62 | exit 1 63 | fi 64 | 65 | #----------------------------------- 66 | # Install Python dependencies 67 | #----------------------------------- 68 | source $JARVIS_DIR/$VIRTUAL_ENV/bin/activate 69 | 70 | activated_python_version=$(python -V) 71 | echo "${green} ${activated_python_version} activated!${reset}" 72 | 73 | # Install python requirements 74 | pip3 install --upgrade cython 75 | pip3 install wheel 76 | python setup.py bdist_wheel 77 | pip3 install -r $JARVIS_DIR/requirements.txt 78 | pip3 install -U scikit-learn 79 | 80 | RESULT=$? 81 | if [ $RESULT -eq 0 ]; then 82 | echo "${green} Install Python dependencies succeeded! ${reset}" 83 | else 84 | echo "${red} Install Python dependencies failed ${reset}" 85 | exit 1 86 | fi 87 | 88 | #----------------------------------- 89 | # Install nltk dependencies 90 | #----------------------------------- 91 | python3 -c "import nltk; nltk.download('punkt'); nltk.download('averaged_perceptron_tagger')" 92 | 93 | RESULT=$? 94 | if [ $RESULT -eq 0 ]; then 95 | echo "${green} Install nltk dependencies succeeded! ${reset}" 96 | else 97 | echo "${red} Install nltk dependencies failed ${reset}" 98 | exit 1 99 | fi 100 | 101 | #----------------------------------- 102 | # Create log access 103 | #----------------------------------- 104 | sudo touch /var/log/jarvis.log && \ 105 | sudo chmod 777 /var/log/jarvis.log 106 | 107 | RESULT=$? 108 | if [ $RESULT -eq 0 ]; then 109 | echo "${green} Create log access succeeded! ${reset}" 110 | else 111 | echo "${red}Create log access failed ${reset}" 112 | exit 1 113 | fi 114 | 115 | #----------------------------------- 116 | # Deactivate virtualenv 117 | #----------------------------------- 118 | deactivate 119 | 120 | #----------------------------------- 121 | # Finished 122 | #----------------------------------- 123 | echo "${green} Jarvis setup succeed! ${reset}" 124 | echo "Start Jarvis: bash run_jarvis.sh" 125 | -------------------------------------------------------------------------------- /src/jarvis/jarvis/skills/collection/datetime.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2019 Georgios Papachristou 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | from datetime import datetime, date 24 | from jarvis.skills.skill import AssistantSkill 25 | 26 | hour_mapping = {'0': 'twelve', 27 | '1': 'one', 28 | '2': 'two', 29 | '3': 'three', 30 | '4': 'four', 31 | '5': 'five', 32 | '6': 'six', 33 | '7': 'seven', 34 | '8': 'eight', 35 | '9': 'nine', 36 | '10': 'ten', 37 | '11': 'eleven', 38 | '12': 'twelve', 39 | 40 | } 41 | 42 | 43 | class DatetimeSkills(AssistantSkill): 44 | 45 | @classmethod 46 | def tell_the_time(cls, **kwargs): 47 | """ 48 | Tells ths current time 49 | """ 50 | 51 | now = datetime.now() 52 | hour, minute = now.hour, now.minute 53 | converted_time = cls._time_in_text(hour, minute) 54 | cls.response('The current time is: {0}'.format(converted_time)) 55 | 56 | @classmethod 57 | def tell_the_date(cls, **kwargs): 58 | """ 59 | Tells ths current date 60 | """ 61 | 62 | today = date.today() 63 | cls.response('The current date is: {0}'.format(today)) 64 | 65 | @classmethod 66 | def _get_12_hour_period(cls, hour): 67 | return 'pm' if 12 <= hour < 24 else 'am' 68 | 69 | @classmethod 70 | def _convert_12_hour_format(cls, hour): 71 | return hour - 12 if 12 < hour <= 24 else hour 72 | 73 | @classmethod 74 | def _create_hour_period(cls, hour): 75 | hour_12h_format = cls._convert_12_hour_format(hour) 76 | period = cls._get_12_hour_period(hour) 77 | return hour_mapping[str(hour_12h_format)] + ' ' + '(' + period + ')' 78 | 79 | @classmethod 80 | def _time_in_text(cls, hour, minute): 81 | 82 | if minute == 0: 83 | time = cls._create_hour_period(hour) + " o'clock" 84 | elif minute == 15: 85 | time = "quarter past " + cls._create_hour_period(hour) 86 | elif minute == 30: 87 | time = "half past " + cls._create_hour_period(hour) 88 | elif minute == 45: 89 | hour_12h_format = cls._convert_12_hour_format(hour + 1) 90 | period = cls._get_12_hour_period(hour) 91 | time = "quarter to " + hour_mapping[str(hour_12h_format)] + ' ' + '(' + period + ')' 92 | elif 0 < minute < 30: 93 | time = str(minute) + " minutes past " + cls._create_hour_period(hour) 94 | else: 95 | hour_12h_format = cls._convert_12_hour_format(hour + 1) 96 | period = cls._get_12_hour_period(hour) 97 | time = str(60 - minute) + " minutes to " + hour_mapping[str(hour_12h_format)] + ' ' + '(' + period + ')' 98 | 99 | return time 100 | -------------------------------------------------------------------------------- /src/jarvis/jarvis/engines/stt.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2019 Georgios Papachristou 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | import logging 24 | import speech_recognition as sr 25 | 26 | import jarvis 27 | from jarvis.core.console import ConsoleManager 28 | 29 | 30 | class STTEngine: 31 | """ 32 | Speech To Text Engine (STT) 33 | 34 | Google API Speech recognition settings 35 | SpeechRecognition API : https://pypi.org/project/SpeechRecognition/2.1.3 36 | """ 37 | 38 | def __init__(self): 39 | super().__init__() 40 | self.console_manager = ConsoleManager() 41 | self.console_manager.console_output(info_log="Configuring Mic..") 42 | self.recognizer = sr.Recognizer() 43 | self.recognizer.pause_threshold = 0.5 44 | self.microphone = sr.Microphone() 45 | self.console_manager.console_output(info_log="Microphone configured successfully!") 46 | 47 | def recognize_input(self, already_activated=False): 48 | """ 49 | Recognize input from mic and returns transcript if activation tag (assistant name) exist 50 | """ 51 | 52 | while True: 53 | transcript = self._recognize_speech_from_mic() 54 | if already_activated or self._activation_name_exist(transcript): 55 | transcript = self._remove_activation_word(transcript) 56 | return transcript 57 | 58 | def _recognize_speech_from_mic(self, ): 59 | """ 60 | Capture the words from the recorded audio (audio stream --> free text). 61 | Transcribe speech from recorded from `microphone`. 62 | """ 63 | 64 | with self.microphone as source: 65 | self.recognizer.adjust_for_ambient_noise(source) 66 | audio = self.recognizer.listen(source) 67 | 68 | try: 69 | transcript = self.recognizer.recognize_google(audio).lower() 70 | self.console_manager.console_output(info_log='User said: {0}'.format(transcript)) 71 | except sr.UnknownValueError: 72 | # speech was unintelligible 73 | transcript = '' 74 | self.console_manager.console_output(info_log='Unable to recognize speech', refresh_console=False) 75 | except sr.RequestError: 76 | # API was unreachable or unresponsive 77 | transcript = '' 78 | self.console_manager.console_output(error_log='Google API was unreachable') 79 | return transcript 80 | 81 | @staticmethod 82 | def _activation_name_exist(transcript): 83 | """ 84 | Identifies the assistant name in the input transcript. 85 | """ 86 | 87 | if transcript: 88 | transcript_words = transcript.split() 89 | return bool(set(transcript_words).intersection([jarvis.assistant_name])) 90 | else: 91 | return False 92 | 93 | @staticmethod 94 | def _remove_activation_word(transcript): 95 | transcript = transcript.replace(jarvis.assistant_name, '') 96 | return transcript 97 | -------------------------------------------------------------------------------- /src/jarvis/jarvis/settings.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2019 Georgios Papachristou 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | from jarvis.enumerations import InputMode 24 | 25 | ROOT_LOG_CONF = { 26 | 'version': 1, 27 | 'root': { 28 | 'level': 'INFO', 29 | 'handlers': ['file'], 30 | }, 31 | 'handlers': { 32 | 'file': { 33 | 'class': 'logging.handlers.RotatingFileHandler', 34 | 'level': 'DEBUG', 35 | 'formatter': 'detailed', 36 | 'filename': '/var/log/jarvis.log', 37 | 'mode': 'a', 38 | 'maxBytes': 10000000, 39 | 'backupCount': 3, 40 | }, 41 | }, 42 | 'formatters': { 43 | 'detailed': { 44 | 'format': '%(asctime)s - %(name)s - %(levelname)s - %(message)s' 45 | }, 46 | } 47 | } 48 | 49 | 50 | """ 51 | Default General assistant settings 52 | These values load in the MongoDB as the default values. 53 | You can change the values when you start the assistant. 54 | The application will ask you 'Do you want to configure it again (y/n)' 55 | I you write yes you can change the following settings or you can change the following default values. 56 | 57 | Keys Description: 58 | - assistant_name: Assistant name works as an activation word 59 | - enabled_period: A period (in seconds) that assistant is waked up (no need of other activation word) 60 | - input_mode: A mode could be 'InputMode.TEXT.value' or 'InputMode.VOICE.value'. 61 | In 'InputMode.TEXT.value' mode, the assistant waits to write in the console, 62 | and in 'InputMode.VOICE.value' to speak in configured mic. 63 | - response_in_text: If True: The assistant will print in the console the response 64 | - response_in_speech: If True: The assistant will produce voice response via audio output. 65 | 66 | """ 67 | 68 | 69 | DEFAULT_GENERAL_SETTINGS = { 70 | 'assistant_name': 'Jarvis', 71 | 'input_mode': InputMode.TEXT.value, 72 | 'response_in_speech': False, 73 | } 74 | 75 | 76 | SKILL_ANALYZER = { 77 | 'args': { 78 | "stop_words": None, 79 | "lowercase": True, 80 | "norm": 'l1', 81 | "use_idf": False, 82 | }, 83 | 'sensitivity': 0.2, 84 | 85 | } 86 | 87 | 88 | """ 89 | Google text to speech API settings 90 | 91 | """ 92 | GOOGLE_SPEECH = { 93 | 94 | 'lang': "en" 95 | } 96 | 97 | 98 | """ 99 | Open weather map API settings 100 | Create key: https://openweathermap.org/appid 101 | 102 | """ 103 | WEATHER_API = { 104 | 'unit': 'celsius', 105 | 'key': None 106 | } 107 | 108 | 109 | """ 110 | WolframAlpha API settings 111 | Create key: https://developer.wolframalpha.com/portal/myapps/ 112 | 113 | """ 114 | WOLFRAMALPHA_API = { 115 | 'key': None 116 | } 117 | 118 | 119 | """ 120 | IPSTACK API settings 121 | Create key: https://ipstack.com/signup/free 122 | 123 | """ 124 | IPSTACK_API = { 125 | 'key': None 126 | } 127 | -------------------------------------------------------------------------------- /src/jarvis/jarvis/skills/collection/general.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2019 Georgios Papachristou 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | import subprocess 24 | 25 | from jarvis.skills.skill import AssistantSkill 26 | import jarvis 27 | 28 | 29 | def get_master_volume(): 30 | stdout, stderr = subprocess.Popen('/usr/bin/amixer sget Master', shell=True, 31 | stdout=subprocess.PIPE).communicate() 32 | list_len = len(str(stdout).split('\n')) 33 | amixer_stdout = str(stdout).split('\n')[list_len - 1] 34 | 35 | find_start = amixer_stdout.find('[') + 1 36 | find_end = amixer_stdout.find('%]', find_start) 37 | 38 | return float(amixer_stdout[find_start:find_end]) 39 | 40 | 41 | def set_master_volume(volume): 42 | val = float(int(volume)) 43 | 44 | proc = subprocess.Popen('/usr/bin/amixer sset Master ' + str(val) + '%', shell=True, stdout=subprocess.PIPE) 45 | proc.wait() 46 | 47 | 48 | class UtilSkills(AssistantSkill): 49 | 50 | @classmethod 51 | def speech_interruption(cls, **kwargs): 52 | """ 53 | Stop assistant speech. 54 | """ 55 | jarvis.output_engine.stop_speaking = True 56 | 57 | @classmethod 58 | def clear_console(cls, **kwargs): 59 | cls.console("Sure") 60 | 61 | @classmethod 62 | def increase_master_volume(cls, **kwargs): 63 | # Limits: Playback 0 - 31 64 | step = 2 65 | volume = get_master_volume() 66 | if volume > 31: 67 | cls.response("The speakers volume is already max") 68 | 69 | increased_volume = volume + step 70 | if increased_volume > 31: 71 | set_master_volume(31) 72 | else: 73 | set_master_volume(increased_volume) 74 | cls.response("I increased the speakers volume") 75 | 76 | @classmethod 77 | def reduce_master_volume(cls, **kwargs): 78 | # Limits: Playback 0 - 31 79 | step = 2 80 | volume = get_master_volume() 81 | if volume < 0: 82 | cls.response("The speakers volume is already muted") 83 | 84 | reduced_volume = volume - step 85 | if reduced_volume < 0: 86 | set_master_volume(0) 87 | else: 88 | set_master_volume(reduced_volume) 89 | cls.response("I reduced the speakers volume") 90 | 91 | @classmethod 92 | def mute_master_volume(cls, **kwargs): 93 | # Limits: Playback 0 - 31 94 | volume = get_master_volume() 95 | if volume == 0: 96 | cls.response("The speakers volume is already muted") 97 | else: 98 | set_master_volume(0) 99 | cls.response("I mute the master speakers") 100 | 101 | @classmethod 102 | def max_master_volume(cls, **kwargs): 103 | # Limits: Playback 0 - 31 104 | volume = get_master_volume() 105 | if volume == 31: 106 | cls.response("The speakers volume is already max") 107 | else: 108 | set_master_volume(31) 109 | cls.response("I set max the master speakers") 110 | -------------------------------------------------------------------------------- /src/jarvis/jarvis/skills/collection/weather.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2019 Georgios Papachristou 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | import re 24 | 25 | from pyowm import OWM 26 | 27 | from jarvis.settings import WEATHER_API 28 | from jarvis.skills.collection.location import LocationSkill 29 | from jarvis.skills.collection.internet import InternetSkills 30 | from jarvis.skills.skill import AssistantSkill 31 | 32 | 33 | class WeatherSkills(AssistantSkill): 34 | 35 | @classmethod 36 | def tell_the_weather(cls, voice_transcript, skill): 37 | """ 38 | Tells the weather of a place 39 | :param tag: string (e.g 'weather') 40 | :param voice_transcript: string (e.g 'weather in London') 41 | 42 | NOTE: If you have the error: 'Reason: Unable to find the resource', try another location 43 | e.g weather in London 44 | """ 45 | tags = cls.extract_tags(voice_transcript, skill['tags']) 46 | for tag in tags: 47 | reg_ex = re.search(tag + ' [a-zA-Z][a-zA-Z] ([a-zA-Z]+)', voice_transcript) 48 | try: 49 | if WEATHER_API['key']: 50 | city = cls._get_city(reg_ex) 51 | if city: 52 | status, temperature = cls._get_weather_status_and_temperature(city) 53 | if status and temperature: 54 | cls.response("Current weather in %s is %s.\n" 55 | "The maximum temperature is %0.2f degree celcius. \n" 56 | "The minimum temperature is %0.2f degree celcius." 57 | % (city, status, temperature['temp_max'], temperature['temp_min']) 58 | ) 59 | else: 60 | cls.response("Sorry the weather API is not available now..") 61 | else: 62 | cls.response("Sorry, no location for weather, try again..") 63 | else: 64 | cls.response("Weather forecast is not working.\n" 65 | "You can get an Weather API key from: https://openweathermap.org/appid") 66 | except Exception as e: 67 | if InternetSkills.internet_availability(): 68 | # If there is an error but the internet connect is good, then the weather API has problem 69 | cls.console_manager.console_output(error_log=e) 70 | cls.response("I faced an issue with the weather site..") 71 | 72 | @classmethod 73 | def _get_weather_status_and_temperature(cls, city): 74 | owm = OWM(API_key=WEATHER_API['key']) 75 | if owm.is_API_online(): 76 | obs = owm.weather_at_place(city) 77 | weather = obs.get_weather() 78 | status = weather.get_status() 79 | temperature = weather.get_temperature(WEATHER_API['unit']) 80 | return status, temperature 81 | else: 82 | return None, None 83 | 84 | @classmethod 85 | def _get_city(cls, reg_ex): 86 | if not reg_ex: 87 | cls.console(info_log='Identify your location..') 88 | city, latitude, longitude = LocationSkill.get_location() 89 | if city: 90 | cls.console(info_log='You location is: {0}'.format(city)) 91 | else: 92 | cls.console(error_log="I couldn't find your location") 93 | else: 94 | city = reg_ex.group(1) 95 | return city 96 | -------------------------------------------------------------------------------- /src/tests/skill_analyzer_tests.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2019 Georgios Papachristou 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | import unittest 24 | from sklearn.feature_extraction.text import TfidfVectorizer 25 | from sklearn.metrics.pairwise import cosine_similarity 26 | 27 | from jarvis import settings 28 | from jarvis.skills.registry import CONTROL_SKILLS, BASIC_SKILLS, ENABLED_BASIC_SKILLS 29 | from jarvis.enumerations import MongoCollections 30 | from jarvis.skills.analyzer import SkillAnalyzer 31 | from jarvis.utils.mongoDB import db 32 | 33 | 34 | class TestSkillMatching(unittest.TestCase): 35 | 36 | def setUp(self): 37 | 38 | all_skills = { 39 | MongoCollections.CONTROL_SKILLS.value: CONTROL_SKILLS, 40 | MongoCollections.ENABLED_BASIC_SKILLS.value: ENABLED_BASIC_SKILLS, 41 | } 42 | for collection, documents in all_skills.items(): 43 | db.update_collection(collection, documents) 44 | 45 | default_assistant_name = settings.DEFAULT_GENERAL_SETTINGS['assistant_name'] 46 | default_input_mode = settings.DEFAULT_GENERAL_SETTINGS['input_mode'] 47 | default_response_in_speech = settings.DEFAULT_GENERAL_SETTINGS['response_in_speech'] 48 | 49 | default_settings = { 50 | 'assistant_name': default_assistant_name, 51 | 'input_mode': default_input_mode, 52 | 'response_in_speech': default_response_in_speech, 53 | } 54 | 55 | db.update_collection(collection=MongoCollections.GENERAL_SETTINGS.value, documents=[default_settings]) 56 | 57 | self.skill_analyzer = SkillAnalyzer( 58 | weight_measure=TfidfVectorizer, 59 | similarity_measure=cosine_similarity, 60 | args=settings.SKILL_ANALYZER.get('args'), 61 | sensitivity=settings.SKILL_ANALYZER.get('sensitivity'), 62 | ) 63 | 64 | def test_all_skill_matches(self): 65 | """ 66 | In this test we examine the matches or ALL skill tags with the functions 67 | If all skills matched right then the test passes otherwise not. 68 | 69 | At the end we print a report with all the not matched cases. 70 | 71 | """ 72 | 73 | verifications_errors = [] 74 | 75 | for basic_skill in BASIC_SKILLS: 76 | print('--------------------------------------------------------------------------------------') 77 | print('Examine skill: {0}'.format(basic_skill.get('name'))) 78 | for tag in basic_skill.get('tags',).split(','): 79 | # If the skill has matching tags 80 | if tag: 81 | expected_skill = basic_skill.get('name') 82 | actual_skill = self.skill_analyzer.extract(tag).get('name') 83 | try: 84 | self.assertEqual(expected_skill, actual_skill) 85 | except AssertionError as e: 86 | verifications_errors.append({'tag': tag, 'error': e}) 87 | 88 | print('-------------------------------------- SKILLS MATCHING REPORT --------------------------------------') 89 | if verifications_errors: 90 | for increment, e in enumerate(verifications_errors): 91 | print('{0})'.format(increment)) 92 | print('Not correct match with tag: {0}'.format(e.get('tag'))) 93 | print('Assertion values (expected != actual): {0}'.format(e.get('error'))) 94 | raise AssertionError 95 | else: 96 | print('All skills matched correctly!') 97 | -------------------------------------------------------------------------------- /src/jarvis/jarvis/core/nlp.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2019 Georgios Papachristou 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the 'Software'), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | import re 24 | import nltk 25 | import logging 26 | 27 | 28 | class NLP: 29 | 30 | def __init__(self): 31 | pass 32 | 33 | @staticmethod 34 | def is_positive_answer(answer): 35 | return answer in ['yes', 'y', 'oui', 'true'] 36 | 37 | @staticmethod 38 | def is_negative_answer(answer): 39 | return answer in ['no', 'n'] 40 | 41 | @staticmethod 42 | def create_parts_of_speech(text): 43 | tokens = nltk.word_tokenize(text) 44 | return nltk.pos_tag(tokens) 45 | 46 | @staticmethod 47 | def is_question_with_modal(parts_of_speech): 48 | grammar = 'QS: {}' 49 | cp = nltk.RegexpParser(grammar) 50 | result = cp.parse(parts_of_speech) 51 | for subtree in result.subtrees(): 52 | if subtree.label() in ['MD', 'WD', 'QS']: 53 | return True 54 | 55 | @staticmethod 56 | def is_question_with_inversion(parts_of_speech): 57 | grammar = 'QS: {}' 58 | cp = nltk.RegexpParser(grammar) 59 | result = cp.parse(parts_of_speech) 60 | for subtree in result.subtrees(): 61 | if subtree.label() in ['QS']: 62 | return True 63 | 64 | @staticmethod 65 | def _extract_verb(parts_of_speech): 66 | for part in parts_of_speech: 67 | if part[1] in ['VB']: 68 | return part[0] 69 | return ' ' 70 | 71 | @staticmethod 72 | def _extract_modal(parts_of_speech): 73 | for part in parts_of_speech: 74 | if part[1] in ['MD']: 75 | return part[0] 76 | return ' ' 77 | 78 | @staticmethod 79 | def _extract_noun(parts_of_speech): 80 | for part in parts_of_speech: 81 | if part[1] in ['NN', 'NNS', 'NNP', 'NNPS']: 82 | return part[0] 83 | return ' ' 84 | 85 | 86 | class ResponseCreator(NLP): 87 | def __init__(self): 88 | super().__init__() 89 | 90 | def create_positive_response(self, sentence): 91 | positive_response = self._create_response(sentence) 92 | if positive_response: 93 | return 'Yes, ' + positive_response 94 | 95 | def create_negative_response(self, sentence): 96 | negative_response = self._create_response(sentence) 97 | if negative_response: 98 | return 'No, ' + negative_response 99 | 100 | def _create_response(self, sentence): 101 | """ 102 | Construct Response Body 103 | :param sentence: string 104 | :return: string 105 | """ 106 | parts_of_speech = self.create_parts_of_speech(sentence) 107 | 108 | # -------------------- 109 | # Extract speech parts 110 | # -------------------- 111 | verb = self._extract_verb(parts_of_speech) 112 | modal = self._extract_modal(parts_of_speech) 113 | noun = self._extract_noun(parts_of_speech) 114 | 115 | # ---------------------------- 116 | # Command type classification 117 | # ---------------------------- 118 | if self.is_question_with_modal(parts_of_speech): 119 | logging.info('The user speech has a modal question') 120 | answer = 'I ' + modal + ' ' + verb + ' ' + noun 121 | elif self.is_question_with_inversion(parts_of_speech): 122 | logging.info('The user speech has an inverse question') 123 | answer = 'I ' + ' ' + verb + ' ' + noun 124 | else: 125 | logging.info('Unclassified user command..') 126 | answer = '' 127 | 128 | return re.sub('\s\s+', ' ', answer) 129 | -------------------------------------------------------------------------------- /src/jarvis/jarvis/utils/startup.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2019 Georgios Papachristou 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | import os 24 | import time 25 | import requests 26 | import logging 27 | from playsound import playsound 28 | 29 | from jarvis.utils import console 30 | from jarvis.enumerations import MongoCollections 31 | from jarvis.core.console import ConsoleManager 32 | 33 | 34 | def play_activation_sound(): 35 | """ 36 | Plays a sound when the assistant enables. 37 | """ 38 | utils_dir = os.path.dirname(__file__) 39 | activation_soundfile = os.path.join(utils_dir, '..', 'files', 'activation_sound.wav') 40 | playsound(activation_soundfile) 41 | 42 | 43 | def internet_connectivity_check(url='http://www.google.com/', timeout=2): 44 | """ 45 | Checks for internet connection availability based on google page. 46 | """ 47 | console_manager = ConsoleManager() 48 | try: 49 | console_manager.console_output(info_log='Checking internet connection..') 50 | _ = requests.get(url, timeout=timeout) 51 | console_manager.console_output(info_log='Internet connection passed!') 52 | return True 53 | except requests.ConnectionError: 54 | console_manager.console_output(warn_log="No internet connection.") 55 | console_manager.console_output(warn_log="Skills with internet connection will not work") 56 | return False 57 | 58 | 59 | def configure_MongoDB(db, settings): 60 | 61 | # ------------------------------------------------------------------------------------------------------------------ 62 | # Load settings 63 | # ------------------------------------------------------------------------------------------------------------------ 64 | 65 | # Only in first time or if 'general_settings' collection is deleted 66 | if db.is_collection_empty(collection=MongoCollections.GENERAL_SETTINGS.value): 67 | console.print_console_header() 68 | print('First time configuration..') 69 | console.print_console_header() 70 | time.sleep(1) 71 | 72 | default_assistant_name = settings.DEFAULT_GENERAL_SETTINGS['assistant_name'] 73 | default_input_mode = settings.DEFAULT_GENERAL_SETTINGS['input_mode'] 74 | default_response_in_speech = settings.DEFAULT_GENERAL_SETTINGS['response_in_speech'] 75 | 76 | new_settings = { 77 | 'assistant_name': default_assistant_name, 78 | 'input_mode': default_input_mode, 79 | 'response_in_speech': default_response_in_speech, 80 | } 81 | 82 | try: 83 | db.update_collection(collection=MongoCollections.GENERAL_SETTINGS.value, documents=[new_settings]) 84 | console.print_console_header('Assistant Name') 85 | print('Assistant name- {0} configured successfully!'.format(default_assistant_name.lower())) 86 | print('Input mode - {0} configured successfully!'.format(default_input_mode)) 87 | print('Speech response output- {0} configured successfully!'.format(default_response_in_speech)) 88 | time.sleep(2) 89 | 90 | except Exception as e: 91 | logging.error('Failed to configure assistant settings with error message {0}'.format(e)) 92 | 93 | # ------------------------------------------------------------------------------------------------------------------ 94 | # Load skills 95 | # ------------------------------------------------------------------------------------------------------------------ 96 | 97 | from jarvis.skills.registry import CONTROL_SKILLS, ENABLED_BASIC_SKILLS 98 | 99 | all_skills = { 100 | MongoCollections.CONTROL_SKILLS.value: CONTROL_SKILLS, 101 | MongoCollections.ENABLED_BASIC_SKILLS.value: ENABLED_BASIC_SKILLS, 102 | } 103 | for collection, documents in all_skills.items(): 104 | db.update_collection(collection, documents) 105 | -------------------------------------------------------------------------------- /src/jarvis/jarvis/engines/tts.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2019 Georgios Papachristou 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | import threading 24 | import logging 25 | import pyttsx3 26 | import queue 27 | 28 | from jarvis.core.console import ConsoleManager 29 | 30 | 31 | class TTS: 32 | """ 33 | Text To Speech Engine (TTS) 34 | """ 35 | 36 | def __init__(self): 37 | self.tts_engine = self._set_voice_engine() 38 | 39 | def run_engine(self): 40 | try: 41 | self.tts_engine.runAndWait() 42 | except RuntimeError: 43 | pass 44 | 45 | @staticmethod 46 | def _set_voice_engine(): 47 | """ 48 | Setup text to speech engine 49 | :return: gtts engine object 50 | """ 51 | tts_engine = pyttsx3.init() 52 | tts_engine.setProperty('rate', 160) # Setting up new voice rate 53 | tts_engine.setProperty('volume', 1.0) # Setting up volume level between 0 and 1 54 | return tts_engine 55 | 56 | 57 | class TTSEngine(TTS): 58 | def __init__(self): 59 | super().__init__() 60 | self.logger = logging 61 | self.message_queue = queue.Queue(maxsize=9) # Maxsize is the size of the queue / capacity of messages 62 | self.stop_speaking = False 63 | self.console_manager = ConsoleManager() 64 | 65 | def assistant_response(self, message, refresh_console=True): 66 | """ 67 | Assistant response in voice. 68 | :param refresh_console: boolean 69 | :param message: string 70 | """ 71 | self._insert_into_message_queue(message) 72 | try: 73 | speech_tread = threading.Thread(target=self._speech_and_console, args=(refresh_console,)) 74 | speech_tread.start() 75 | except RuntimeError as e: 76 | self.logger.error('Error in assistant response thread with message {0}'.format(e)) 77 | 78 | def _insert_into_message_queue(self, message): 79 | try: 80 | self.message_queue.put(message) 81 | except Exception as e: 82 | self.logger.error("Unable to insert message to queue with error message: {0}".format(e)) 83 | 84 | def _speech_and_console(self, refresh_console): 85 | """ 86 | Speech method translate text batches to speech and print them in the console. 87 | :param text: string (e.g 'tell me about google') 88 | """ 89 | try: 90 | while not self.message_queue.empty(): 91 | 92 | cumulative_batch = '' 93 | message = self.message_queue.get() 94 | if message: 95 | batches = self._create_text_batches(raw_text=message) 96 | for batch in batches: 97 | self.tts_engine.say(batch) 98 | cumulative_batch += batch 99 | self.console_manager.console_output(cumulative_batch, refresh_console=refresh_console) 100 | self.run_engine() 101 | if self.stop_speaking: 102 | self.logger.debug('Speech interruption triggered') 103 | self.stop_speaking = False 104 | break 105 | except Exception as e: 106 | self.logger.error("Speech and console error message: {0}".format(e)) 107 | 108 | @staticmethod 109 | def _create_text_batches(raw_text, number_of_words_per_batch=8): 110 | """ 111 | Splits the user speech message into batches and return a list with the split batches 112 | :param raw_text: string 113 | :param number_of_words_per_batch: int 114 | :return: list 115 | """ 116 | raw_text = raw_text + ' ' 117 | list_of_batches = [] 118 | total_words = raw_text.count(' ') 119 | letter_id = 0 120 | 121 | for split in range(0, int(total_words / number_of_words_per_batch)): 122 | batch = '' 123 | words_count = 0 124 | while words_count < number_of_words_per_batch: 125 | batch += raw_text[letter_id] 126 | if raw_text[letter_id] == ' ': 127 | words_count += 1 128 | letter_id += 1 129 | list_of_batches.append(batch) 130 | 131 | if letter_id < len(raw_text): # Add the rest of word in a batch 132 | list_of_batches.append(raw_text[letter_id:]) 133 | return list_of_batches 134 | -------------------------------------------------------------------------------- /src/jarvis/jarvis/skills/collection/info.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2019 Georgios Papachristou 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | from jarvis.skills.skill import AssistantSkill 24 | from jarvis.utils.mongoDB import db 25 | from jarvis.utils.console import headerize 26 | 27 | basic_skills_format = """ 28 | ----------------------------------------------------------------------- 29 | - Basic Skills - 30 | ----------------------------------------------------------------------- 31 | """ 32 | 33 | basic_skills_body_format = """ 34 | ---------------------------- Skill ({0}) ---------------------------- 35 | * Skill func: {1} 36 | * Description: {2} 37 | * Tags: {3} 38 | """ 39 | 40 | learned_skills_format = """ 41 | ----------------------------------------------------------------------- 42 | - Learned Skills - 43 | ----------------------------------------------------------------------- 44 | """ 45 | 46 | learned_skills_body_format = """ 47 | -------------------------- Learned Skill ({0}) ------------------------ 48 | * Skill func: {1} 49 | * Question: {2} 50 | * Response: {3} 51 | """ 52 | 53 | 54 | class AssistantInfoSkills(AssistantSkill): 55 | 56 | @classmethod 57 | def assistant_check(cls, **kwargs): 58 | """ 59 | Responses that assistant can hear the user. 60 | """ 61 | cls.response('Hey human!') 62 | 63 | @classmethod 64 | def tell_the_skills(cls, **kwargs): 65 | """ 66 | Tells what he can do as assistant. 67 | """ 68 | try: 69 | response_base = 'I can do the following: \n\n' 70 | response = cls._create_skill_response(response_base) 71 | cls.response(response) 72 | except Exception as e: 73 | cls.console(error_log="Error with the execution of skill with message {0}".format(e)) 74 | cls.response("Sorry I faced an issue") 75 | 76 | @classmethod 77 | def assistant_help(cls, **kwargs): 78 | """ 79 | Assistant help prints valuable information about the application. 80 | 81 | """ 82 | cls.console(headerize('Help')) 83 | response_base = '' 84 | try: 85 | response = cls._create_skill_response(response_base) 86 | cls.console(response) 87 | except Exception as e: 88 | cls.console(error_log="Error with the execution of skill with message {0}".format(e)) 89 | cls.response("Sorry I faced an issue") 90 | 91 | @classmethod 92 | def _create_skill_response(cls, response): 93 | 94 | # -------------------------------------------------------------------------------------------------------------- 95 | # For existing skills (basic skills) 96 | # -------------------------------------------------------------------------------------------------------------- 97 | basic_skills = db.get_documents(collection='enabled_basic_skills') 98 | response = response + basic_skills_format 99 | for skill_id, skill in enumerate(basic_skills, start=1): 100 | response = response + basic_skills_body_format.format(skill_id, 101 | skill.get('name'), 102 | skill.get('description'), 103 | skill.get('tags') 104 | ) 105 | 106 | # -------------------------------------------------------------------------------------------------------------- 107 | # For learned skills (created from 'learn' skill) 108 | # -------------------------------------------------------------------------------------------------------------- 109 | skills = db.get_documents(collection='learned_skills') 110 | response = response + learned_skills_format 111 | for skill_id, skill in enumerate(skills, start=1): 112 | response = response + learned_skills_body_format.format(skill_id, 113 | skill.get('name'), 114 | skill.get('tags'), 115 | skill.get('response') 116 | ) 117 | 118 | return response 119 | -------------------------------------------------------------------------------- /src/jarvis/jarvis/core/processor.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2019 Georgios Papachristou 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | import jarvis 24 | 25 | from sklearn.feature_extraction.text import TfidfVectorizer 26 | from sklearn.metrics.pairwise import cosine_similarity 27 | 28 | from jarvis.skills.analyzer import SkillAnalyzer 29 | from jarvis.skills.registry import skill_objects 30 | from jarvis.core.nlp import ResponseCreator 31 | from jarvis.skills.collection.activation import ActivationSkills 32 | from jarvis.utils.mongoDB import db 33 | from jarvis.skills.collection.wolframalpha import WolframSkills 34 | 35 | 36 | class Processor: 37 | def __init__(self, console_manager, settings_): 38 | self.console_manager = console_manager 39 | self.settings = settings_ 40 | self.response_creator = ResponseCreator() 41 | self.skill_analyzer = SkillAnalyzer( 42 | weight_measure=TfidfVectorizer, 43 | similarity_measure=cosine_similarity, 44 | args=self.settings.SKILL_ANALYZER.get('args'), 45 | sensitivity=self.settings.SKILL_ANALYZER.get('sensitivity'), 46 | ) 47 | 48 | def run(self): 49 | """ 50 | Assistant starting point. 51 | 52 | - STEP 1: Get user input based on the input mode (voice or text) 53 | - STEP 2: Matches the input with a skill 54 | - STEP 3: Create a response 55 | - STEP 4: Execute matched skill 56 | - STEP 5: Insert user transcript and response in history collection (in MongoDB) 57 | 58 | """ 59 | 60 | transcript = jarvis.input_engine.recognize_input() 61 | skill = self.skill_analyzer.extract(transcript) 62 | 63 | if skill: 64 | # ---------------------------------------------------------------------------------------------------------- 65 | # Successfully extracted skill 66 | # ---------------------------------------------------------------------------------------------------------- 67 | 68 | # --------------- 69 | # Positive answer 70 | # --------------- 71 | response = self.response_creator.create_positive_response(transcript) 72 | jarvis.output_engine.assistant_response(response) 73 | 74 | # --------------- 75 | # Skill execution 76 | # --------------- 77 | skill_to_execute = {'voice_transcript': transcript, 'skill': skill} 78 | self._execute_skill(skill_to_execute) 79 | 80 | else: 81 | # ---------------------------------------------------------------------------------------------------------- 82 | # No skill extracted 83 | # ---------------------------------------------------------------------------------------------------------- 84 | 85 | # --------------- 86 | # Negative answer 87 | # --------------- 88 | response = self.response_creator.create_negative_response(transcript) 89 | jarvis.output_engine.assistant_response(response) 90 | 91 | # --------------- 92 | # WolframAlpha API Call 93 | # --------------- 94 | skill_to_execute = {'voice_transcript': transcript, 95 | 'skill': {'name': WolframSkills.call_wolframalpha.__name__} 96 | } 97 | 98 | response = WolframSkills.call_wolframalpha(transcript) 99 | 100 | # -------------------------------------------------------------------------------------------------------------- 101 | # Add new record to history 102 | # -------------------------------------------------------------------------------------------------------------- 103 | 104 | record = {'user_transcript': transcript, 105 | 'response': response if response else '--', 106 | 'executed_skill': skill_to_execute if skill_to_execute else '--' 107 | } 108 | 109 | db.insert_many_documents('history', [record]) 110 | 111 | def _execute_skill(self, skill): 112 | if skill: 113 | skill_func_name = skill.get('skill').get('func') 114 | self.console_manager.console_output(info_log='Executing skill {0}'.format(skill_func_name)) 115 | try: 116 | ActivationSkills.enable_assistant() 117 | skill_func_name = skill.get('skill').get('func') 118 | skill_func = skill_objects[skill_func_name] 119 | skill_func(**skill) 120 | except Exception as e: 121 | self.console_manager.console_output(error_log="Failed to execute skill {0} with message: {1}" 122 | .format(skill_func_name, e)) 123 | -------------------------------------------------------------------------------- /src/jarvis/jarvis/skills/collection/reminder.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2019 Georgios Papachristou 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | import os 24 | import re 25 | import time 26 | import datetime 27 | 28 | from threading import Thread 29 | from playsound import playsound 30 | from apscheduler.schedulers.background import BackgroundScheduler 31 | 32 | from jarvis.utils.input import validate_digits_input 33 | from jarvis.skills.skill import AssistantSkill 34 | from jarvis.utils.console import OutputStyler 35 | 36 | time_intervals = { 37 | 'seconds': {'variations': ['sec', 'second', 'seconds'], 38 | 'scheduler_interval': 'seconds' 39 | }, 40 | 'minutes': {'variations': ['minute', 'minutes'], 41 | 'scheduler_interval': 'minutes' 42 | }, 43 | 'hours': {'variations': ['hour', 'hours'], 44 | 'scheduler_interval': 'hours' 45 | }, 46 | 'months': {'variations': ['month', 'months'], 47 | 'scheduler_interval': 'months' 48 | }, 49 | 'years': {'variations': ['year', 'years'], 50 | 'scheduler_interval': 'years' 51 | }, 52 | } 53 | 54 | 55 | class ReminderSkills(AssistantSkill): 56 | 57 | @classmethod 58 | def _get_reminder_duration_and_time_interval(cls, voice_transcript): 59 | """ 60 | Extracts the duration and the time interval from the voice transcript. 61 | 62 | NOTE: If there are multiple time intervals, it will extract the first one. 63 | """ 64 | for time_interval in time_intervals.values(): 65 | for variation in time_interval['variations']: 66 | if variation in voice_transcript: 67 | # Change '[0-9]'to '([0-9])' and now the skill is working 68 | reg_ex = re.search('([0-9])', voice_transcript) 69 | duration = reg_ex.group(1) 70 | return duration, time_interval['scheduler_interval'] 71 | 72 | @classmethod 73 | def create_reminder(cls, voice_transcript, **kwargs): 74 | """ 75 | Creates a simple reminder for the given time interval (seconds or minutes or hours..) 76 | :param voice_transcript: string (e.g 'Make a reminder in 10 minutes') 77 | """ 78 | reminder_duration, scheduler_interval = cls._get_reminder_duration_and_time_interval(voice_transcript) 79 | 80 | def reminder(): 81 | cls.response("Hey, I remind you that now the {0} {1} passed!".format(reminder_duration, scheduler_interval)) 82 | job.remove() 83 | 84 | try: 85 | if reminder_duration: 86 | scheduler = BackgroundScheduler() 87 | interval = {scheduler_interval: int(reminder_duration)} 88 | job = scheduler.add_job(reminder, 'interval', **interval) 89 | cls.response("I have created a reminder in {0} {1}".format(reminder_duration, scheduler_interval)) 90 | scheduler.start() 91 | 92 | except Exception as e: 93 | cls.console(error_log=e) 94 | cls.response("I can't create a reminder") 95 | 96 | @classmethod 97 | def set_alarm(cls, voice_transcript, **kwargs): 98 | 99 | # ------------------------------------------------ 100 | # Current Limitations 101 | # ------------------------------------------------ 102 | 103 | # - User can set alarm only for the same day 104 | # - Works only for specific format hh:mm 105 | # - Alarm sounds for 12 secs and stops, user can't stop interrupt it. 106 | # -- Future improvement is to ring until user stop it. 107 | 108 | cls.response("Yes, I will set an alarm") 109 | 110 | alarm_hour = validate_digits_input(message="Tell me the exact hour", values_range=[0, 24]) 111 | alarm_minutes = validate_digits_input(message="Now tell me the minutes", values_range=[0, 59]) 112 | 113 | try: 114 | thread = Thread(target=cls._alarm_countdown, args=(alarm_hour, alarm_minutes)) 115 | thread.start() 116 | except Exception as e: 117 | cls.console(error_log="Failed to play alarm with error message: {0}".format(e)) 118 | 119 | @classmethod 120 | def _alarm_countdown(cls, alarm_hour, alarm_minutes): 121 | 122 | now = datetime.datetime.now() 123 | 124 | alarm_time = datetime.datetime.combine(now.date(), datetime.time(alarm_hour, alarm_minutes, 0)) 125 | waiting_period = alarm_time - now 126 | 127 | if waiting_period < datetime.timedelta(0): 128 | # Choose 8PM today as the time the alarm fires. 129 | # This won't work well if it's after 8PM, though. 130 | cls.response('This time has past for today') 131 | else: 132 | # Successful setup message 133 | response_message = "Alarm - {0}:{1} for today is configured " \ 134 | + OutputStyler.GREEN + "successfully!" + OutputStyler.ENDC 135 | cls.response(response_message.format(alarm_hour, alarm_minutes)) 136 | 137 | # Alarm countdown starts 138 | time.sleep((alarm_time - now).total_seconds()) 139 | cls.response("Wake up! It's {0}".format(datetime.datetime.now().strftime('%H:%M'))) 140 | 141 | skills_dir = os.path.dirname(__file__) 142 | alarm_soundfile = os.path.join(skills_dir, '..', 'files', 'analog_watch_alarm.wav') 143 | 144 | playsound(alarm_soundfile) 145 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![CodeFactor](https://www.codefactor.io/repository/github/ggeop/python-ai-assistant/badge)](https://www.codefactor.io/repository/github/ggeop/Python-ai-assistant) 2 | [![Maintainability](https://api.codeclimate.com/v1/badges/8c90305e22186cc2c9d5/maintainability)](https://codeclimate.com/github/ggeop/Python-ai-assistant/maintainability) 3 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 4 | [![Build Status](https://app.travis-ci.com/ggeop/Python-ai-assistant.svg?branch=master)](https://app.travis-ci.com/ggeop/Python-ai-assistant) 5 | 6 | ![alt text](https://github.com/ggeop/Python-ai-assistant/blob/master/imgs/jarvis_logo.png) 7 | 8 | # About Jarvis - An Intelligent AI Consciousness 🧠 9 | Jarvis is a voice commanding assistant service in [Python 3.8](https://www.python.org/downloads/release/python-360/) 10 | It can recognize human speech, talk to user and execute basic commands. 11 | 12 | #### Requirements 13 | 14 | * Operation system: **Ubuntu 20.04 (Focal Fossa)** 15 | * Python Version: **3.8.x** 16 | 17 | 18 | #### Assistant Skills 19 | * **Opens a web page** (e.g 'Jarvis open youtube') 20 | * **Play music in Youtube** (e.g 'Jarvis play mozart') 21 | * **Increase/decrease** the speakers master volume (also can set max/mute speakers volume) ** (e.g 'Jarvis volume up!') 22 | * **Opens libreoffice suite applications (calc, writer, impress)** (e.g 'Jarvis open calc') 23 | * **Tells about something**, by searching on the internet (e.g 'Jarvis tells me about oranges') 24 | * **Tells the weather** for a place (e.g 'Jarvis tell_the_skills me the weather in London') 25 | * **Tells the current time and/or date** (e.g 'Jarvis tell me time or date') 26 | * **Set an alarm** (e.g 'Jarvis create a new alarm') 27 | * **Tells the internet speed (ping, uplink and downling)** (e.g 'Jarvis tell_the_skills me the internet speed') 28 | * **Tells the internet availability** (e.g 'Jarvis is the internet connection ok?') 29 | * **Tells the daily news** (e.g 'Jarvis tell me today news') 30 | * **Spells a word** (e.g 'Jarvis spell me the word animal') 31 | * **Creates a reminder** (e.g 'Jarvis create a 10 minutes reminder') 32 | * **Opens linux applications** (e.g 'Jarvis open bash/firefox') 33 | * **Tells everything it can do** (e.g 'Jarvis tell me your skills or tell me what can you do') 34 | * **Tells the current location** (e.g 'Jarvis tell me your current location') 35 | * **Tells how much memory consumes** (e.g 'Jarvis tell me your memory consumption) 36 | * **Tells users commands history** (e.g 'Jarvis tell me my history') 37 | * **Write/tell 'remember' and enable learning mode and add new responses on demand!** (e.g 'Jarvis remember') 38 | * **Clear bash console** (e.g 'Jarvis clear console') 39 | * **Has help command, which prints all the skills with their descriptions** (e.g 'Jarvis help') 40 | * **Do basic calculations** (e.g 'Jarvis (5 + 6) * 8' or 'Jarvis one plus one') 41 | * **Change settings on runtime** (e.g 'Jarvis change settings') 42 | 43 | #### Assistant Features 44 | * **Asynchronous command execution & speech recognition and interpretation** 45 | * Supports **two different user input modes (text or speech)**, user can write or speek in the mic. 46 | * Answers in **general questions** (via call Wolfram API), e.g ('Jarvis tell me the highest building') 47 | * **Change input mode on run time**, triggered by a phrase e.g 'Jarvis change settings') 48 | * Easy **voice-command customization** 49 | * Configurable **assistant name** (e.g 'Jarvis', 'Sofia', 'John' etc.) (change on run time supported) 50 | * **Log preview** in console 51 | * **Vocal or/and text response** 52 | * **Keeps commands history and learned skills** in MongoDB.' 53 | 54 | ## Getting Started 55 | ### Create KEYs for third party APIs 56 | Jarvis assistant uses third party APIs for speech recognition,web information search, weather forecasting etc. 57 | All the following APIs have free no-commercial API calls. Subscribe to the following APIs in order to take FREE access KEYs. 58 | * [OpenWeatherMap](https://openweathermap.org/appid): API for weather forecast. 59 | * [WolframAlpha](https://developer.wolframalpha.com/portal/myapps/): API for answer questions. 60 | * [IPSTACK](https://ipstack.com/signup/free): API for current location. 61 | ### Setup Jarvis in Ubuntu/Debian system 62 | * Download the Jarvis repo locally: 63 | ```bash 64 | git clone https://github.com/ggeop/Jarvis.git --branch master 65 | ``` 66 | 67 | **For Contribution**: 68 | ```bash 69 | git clone https://github.com/ggeop/Jarvis.git --branch develop 70 | ``` 71 | 72 | * Change working directory 73 | ```bash 74 | cd Jarvis 75 | ``` 76 | * Setup Jarvis and system dependencies: 77 | ```bash 78 | bash setup.sh 79 | ``` 80 | 81 | * Put the Keys in settings 82 | 83 | **NOTE:** *For better exprerience, before you start the application you can put the free KEYs in the settings.py* 84 | 85 | ```bash 86 | nano Jarvis/src/jarvis/jarvis/setting.py 87 | ``` 88 | 89 | ### Start voice commanding assistant 90 | ![alt text](https://github.com/ggeop/Jarvis/blob/master/imgs/Jarvis_printscreen.PNG) 91 | 92 | * Start the assistant service: 93 | ```bash 94 | bash run_jarvis.sh 95 | ``` 96 | 97 | ### How to add a new Skill to assistant 98 | You can easily add a new skill in two steps. 99 | * Create a new configurationin SKILLS in **skills/registry.py** 100 | ```python 101 | { 102 | 'enable': True, 103 | 'func': Skills.new_skill, 104 | 'tags': 'tag1, tag2', 105 | 'description': 'skill description..' 106 | } 107 | ``` 108 | * Create a new skill module in **skills/collection** 109 | 110 | ### Desicion Model 111 | ![alt text](https://github.com/ggeop/Jarvis/blob/master/imgs/desicion_model.png) 112 | 113 | ### Extract skill 114 | The skill extraction implement in a matrix of [TF-IDF features](https://scikit-learn.org/stable/modules/generated/sklearn.feature_extraction.text.TfidfVectorizer.html) for each skill. 115 | In the following example he have a dimensional space with three skills. 116 | The user input analyzed in this space and by using a similarity metric (e.g cosine) we find the most similar skill. 117 | ![alt text](https://github.com/ggeop/Jarvis/blob/master/imgs/skill_space_desicion.png) 118 | 119 | --- 120 | 121 | ## Contributing 122 | * Pull Requests (PRs) are welcome :relaxed: 123 | * The process for contribution is the following: 124 | * Clone the project 125 | * Checkout `develop` branch and create a feature branch e.g `feature_branch` 126 | * Open a PR to `develop` 127 | * Wait for review and approval !! 128 | * `master` branch update and release is automated via [Travis CI/CD](https://app.travis-ci.com/github/ggeop/Python-ai-assistant) 129 | * Try to follow PEP-8 guidelines and add useful comments! 130 | 131 | ## CI/CD Flow 132 | ![alt text](https://github.com/ggeop/Python-ai-assistant/blob/master/imgs/TravisFlow.png) 133 | -------------------------------------------------------------------------------- /src/jarvis/jarvis/skills/collection/browser.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2019 Georgios Papachristou 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | import wikipedia 24 | import requests 25 | import time 26 | import re 27 | import urllib.request 28 | import subprocess 29 | import webbrowser 30 | 31 | from bs4 import BeautifulSoup as bs 32 | 33 | from jarvis.skills.skill import AssistantSkill 34 | 35 | 36 | class BrowserSkills(AssistantSkill): 37 | 38 | @classmethod 39 | def tell_me_about(cls, voice_transcript, skill): 40 | """ 41 | Tells about something by searching on wiki. 42 | :param voice_transcript: string (e.g 'about google') 43 | :param skill: dict 44 | """ 45 | tags = cls.extract_tags(voice_transcript, skill['tags']) 46 | only_text_pattern = '([a-zA-Z]+)' 47 | for tag in tags: 48 | reg_ex = re.search(tag + ' ' + only_text_pattern, voice_transcript) 49 | if reg_ex: 50 | topic = reg_ex.group(1) 51 | try: 52 | response = cls._decoded_wiki_response(topic) 53 | cls.response(response) 54 | except Exception as e: 55 | cls.console(error_log="Error with the execution of skill with message {0}".format(e)) 56 | cls.response(" I can't find what you want, and I will open a new tab in browser") 57 | time.sleep(1) 58 | cls._search_on_google(topic) 59 | 60 | @classmethod 61 | def open_in_youtube(cls, voice_transcript, skill): 62 | """ 63 | Open a video in youtube. 64 | :param voice_transcript: string (e.g 'play mozart') 65 | :param skill: dict (e.g 66 | """ 67 | 68 | tags = cls.extract_tags(voice_transcript, skill['tags']) 69 | for tag in tags: 70 | reg_ex = re.search(tag + ' (.*)', voice_transcript) 71 | 72 | try: 73 | if reg_ex: 74 | search_text = reg_ex.group(1) 75 | base = "https://www.youtube.com/results?search_query={0}&orderby=viewCount" 76 | r = requests.get(base.format(search_text.replace(' ', '+'))) 77 | page = r.text 78 | soup = bs(page, 'html.parser') 79 | vids = soup.findAll('a', attrs={'class': 'yt-uix-tile-link'}) 80 | video = 'https://www.youtube.com' + vids[0]['href'] + "&autoplay=1" 81 | cls.console(info_log="Play Youtube video: {0}".format(video)) 82 | subprocess.Popen(["python", "-m", "webbrowser", "-t", video], stdout=subprocess.PIPE, shell=False) 83 | except Exception as e: 84 | cls.console(error_log="Error with the execution of skill with message {0}".format(e)) 85 | cls.response("I can't find what do you want in Youtube..") 86 | 87 | @classmethod 88 | def open_website_in_browser(cls, voice_transcript, skill): 89 | """ 90 | Opens a web page in the browser. 91 | :param voice_transcript: string (e.g 'about google') 92 | :param skill: dict (e.g 93 | 94 | Web page can be in the following formats 95 | * open www.xxxx.com 96 | * open xxxx.com 97 | * open xxxx 98 | 99 | Limitations 100 | - If in the voice_transcript there are more than one commands_dict 101 | e.g voice_transcript='open youtube and open netflix' the application will find 102 | and execute only the first one, in our case will open the youtube. 103 | - Support ONLY the following top domains: '.com', '.org', '.net', '.int', '.edu', '.gov', '.mil' 104 | 105 | """ 106 | tags = cls.extract_tags(voice_transcript, skill['tags']) 107 | domain_regex = '([\.a-zA-Z]+)' 108 | "" 109 | for tag in tags: 110 | reg_ex = re.search(tag + ' ' + domain_regex, voice_transcript) 111 | try: 112 | if reg_ex: 113 | domain = reg_ex.group(1) 114 | url = cls._create_url(domain) 115 | cls.response('Sure') 116 | time.sleep(1) 117 | webbrowser.open_new_tab(url) 118 | cls.response('I opened the {0}'.format(domain)) 119 | except Exception as e: 120 | cls.console(error_log="Error with the execution of skill with message {0}".format(e)) 121 | cls.response("I can't find this domain..") 122 | 123 | @classmethod 124 | def tell_me_today_news(cls, **kwargs): 125 | try: 126 | news_url = "https://news.google.com/news/rss" 127 | client = urllib.request.urlopen(news_url) 128 | xml_page = client.read() 129 | client.close() 130 | soup = bs(xml_page, "xml") 131 | news_list = soup.findAll("item") 132 | response = "" 133 | for news in news_list[:5]: 134 | data = news.title.text.encode('utf-8') + '\n' 135 | response += data.decode() 136 | cls.response(response) 137 | except Exception as e: 138 | cls.console(error_log="Error with the execution of skill with message {0}".format(e)) 139 | cls.response("I can't find about daily news..") 140 | 141 | @classmethod 142 | def _decoded_wiki_response(cls, topic): 143 | """ 144 | Decoding the wiki response. 145 | :param topic: string 146 | :return: string 147 | """ 148 | ny = wikipedia.page(topic) 149 | data = ny.content[:500].encode('utf-8') 150 | response = '' 151 | response += data.decode() 152 | return response 153 | 154 | @classmethod 155 | def _create_url(cls, domain): 156 | """ 157 | Creates a url. It checks if there is .com suffix and add it if it not exist. 158 | :param tag: string (e.g youtube) 159 | :return: string (e.g http://www.youtube.com) 160 | """ 161 | top_level_domains = ['.com', '.org', '.net', '.int', '.edu', '.gov', '.mil'] 162 | url = None 163 | for top_level_domain in top_level_domains: 164 | if re.search(top_level_domain, domain): 165 | url = 'http://' + domain 166 | 167 | url = 'http://www.' + domain + '.com' if not url else url 168 | return url 169 | 170 | @classmethod 171 | def _search_on_google(cls, term): 172 | url = "https://www.google.com.tr/search?q={}".format(term) 173 | try: 174 | webbrowser.open_new_tab(url) 175 | except Exception as e: 176 | cls.console(error_log="Error with the execution of skill with message {0}".format(e)) 177 | cls.response("Sorry I faced an issue with google search") 178 | 179 | -------------------------------------------------------------------------------- /src/jarvis/jarvis/core/console.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2019 Georgios Papachristou 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | import subprocess 24 | import os 25 | import psutil 26 | import logging 27 | 28 | from jarvis import settings 29 | from jarvis.utils.mongoDB import db 30 | from jarvis.utils.console import jarvis_logo, start_text, OutputStyler, headerize 31 | from jarvis.enumerations import MongoCollections, InputMode 32 | 33 | 34 | class ConsoleManager: 35 | def __init__(self): 36 | pass 37 | 38 | def clear(self): 39 | """ 40 | Clears stdout 41 | 42 | Check and make call for specific operating system 43 | """ 44 | 45 | subprocess.call('tput reset' if os.name == 'posix' else 'cls', shell=True) 46 | 47 | def console_output(self, text='', debug_log=None, info_log=None, warn_log=None, error_log=None, refresh_console=True): 48 | """ 49 | This method creates the assistant output. 50 | The output has four sectors: 51 | * GENERAL INFO: Info about assistant settigs 52 | * SYSTEM: System info e.g assistant memory usage 53 | * LOG: Assistant log last lines 54 | * ASSISTANT: Assistant response output 55 | 56 | Output example: 57 | 58 | ██╗ █████╗ ██████╗ ██╗ ██╗██╗███████╗ 59 | ██║██╔══██╗██╔══██╗██║ ██║██║██╔════╝ 60 | ██║███████║██████╔╝██║ ██║██║███████╗ 61 | ██ ██║██╔══██║██╔══██╗╚██╗ ██╔╝██║╚════██║ 62 | ╚█████╔╝██║ ██║██║ ██║ ╚████╔╝ ██║███████║ 63 | ╚════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═══╝ ╚═╝╚══════╝ 64 | NOTE: CTRL + C If you want to Quit. 65 | -------------------------------------- GENERAL INFO --------------------------------------------- 66 | RESPONSE IN SPEECH: NOT ENABLED 67 | INPUT MODE: TEXT 68 | ---------------------------------------- SYSTEM -------------------------------------------------- 69 | RAM USAGE: 0.14 GB 70 | ----------------------------------------- LOG ---------------------------------------------------- 71 | 2020-04-25 19:22:51,524 - root - INFO - Startup checks.. 72 | 2020-04-25 19:22:51,534 - root - DEBUG - Internet connection check.. 73 | 2020-04-25 19:22:51,773 - root - INFO - Internet connection passed! 74 | 2020-04-25 19:22:51,783 - root - INFO - Application started 75 | 76 | ---------------------------------------- ASSISTANT ------------------------------------------------ 77 | > The current date is: 2020-04-25 78 | -------------------------------------------- - ---------------------------------------------------- 79 | """ 80 | 81 | if refresh_console: 82 | self.clear() 83 | 84 | # ---------------------------------------------------------------------------------------------------------- 85 | # Logo sector 86 | # ---------------------------------------------------------------------------------------------------------- 87 | self._stdout_print(jarvis_logo + start_text) 88 | self._stdout_print(" NOTE: CTRL + C If you want to Quit.") 89 | 90 | # ---------------------------------------------------------------------------------------------------------- 91 | # General info sector 92 | # ---------------------------------------------------------------------------------------------------------- 93 | settings_documents = db.get_documents(collection=MongoCollections.GENERAL_SETTINGS.value) 94 | if settings_documents: 95 | settings_ = settings_documents[0] 96 | print(OutputStyler.HEADER + headerize('GENERAL INFO') + OutputStyler.ENDC) 97 | enabled = OutputStyler.GREEN + 'ENABLED' + OutputStyler.ENDC if settings_['response_in_speech'] else OutputStyler.WARNING + 'NOT ENABLED' + OutputStyler.ENDC 98 | print(OutputStyler.BOLD + 'RESPONSE IN SPEECH: ' + enabled) 99 | print(OutputStyler.BOLD + 'INPUT MODE: ' + OutputStyler.GREEN + '{0}'.format(settings_['input_mode'].upper() + OutputStyler.ENDC) + OutputStyler.ENDC) 100 | if settings_['input_mode'] == InputMode.VOICE.value: 101 | print(OutputStyler.BOLD + 'NOTE: ' + OutputStyler.GREEN + "Include " + "'{0}'".format(settings_['assistant_name'].upper()) + " in you command to enable assistant" + OutputStyler.ENDC + OutputStyler.ENDC) 102 | 103 | # ---------------------------------------------------------------------------------------------------------- 104 | # System info sector 105 | # ---------------------------------------------------------------------------------------------------------- 106 | print(OutputStyler.HEADER + headerize('SYSTEM') + OutputStyler.ENDC) 107 | print(OutputStyler.BOLD + 108 | 'RAM USAGE: {0:.2f} GB'.format(self._get_memory()) + OutputStyler.ENDC) 109 | 110 | # ---------------------------------------------------------------------------------------------------------- 111 | # Assistant logs sector 112 | # ---------------------------------------------------------------------------------------------------------- 113 | 114 | if debug_log: 115 | logging.debug(debug_log) 116 | 117 | if info_log: 118 | logging.info(info_log) 119 | 120 | if warn_log: 121 | logging.warning(warn_log) 122 | 123 | if error_log: 124 | logging.error(error_log) 125 | 126 | MAX_NUMBER_OF_LOG_LINES = 25 127 | log_path = settings.ROOT_LOG_CONF['handlers']['file']['filename'] 128 | 129 | lines = subprocess.check_output(['tail', '-' + str(MAX_NUMBER_OF_LOG_LINES), log_path]).decode("utf-8") 130 | actual_number_of_log_lines = len(lines) 131 | print(OutputStyler.HEADER + headerize('LOG -{0} (Total Lines: {1})'.format(log_path, 132 | actual_number_of_log_lines) 133 | ) + OutputStyler.ENDC) 134 | print(OutputStyler.BOLD + lines + OutputStyler.ENDC) 135 | 136 | # ---------------------------------------------------------------------------------------------------------- 137 | # Assistant input/output sector 138 | # ---------------------------------------------------------------------------------------------------------- 139 | print(OutputStyler.HEADER + headerize('ASSISTANT') + OutputStyler.ENDC) 140 | if text: 141 | print(OutputStyler.BOLD + '> ' + text + '\r' + OutputStyler.ENDC) 142 | print(OutputStyler.HEADER + headerize() + OutputStyler.ENDC) 143 | else: 144 | if text: 145 | print(OutputStyler.BOLD + text + '\r' + OutputStyler.ENDC) 146 | 147 | @staticmethod 148 | def _get_memory(): 149 | """ 150 | Get assistant process Memory usage in GB 151 | """ 152 | pid = os.getpid() 153 | py = psutil.Process(pid) 154 | return py.memory_info()[0] / 2. ** 30 155 | 156 | @staticmethod 157 | def _stdout_print(text): 158 | """ 159 | Application stdout with format. 160 | :param text: string 161 | """ 162 | print(OutputStyler.CYAN + text + OutputStyler.ENDC) 163 | -------------------------------------------------------------------------------- /src/jarvis/jarvis/skills/registry.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2019 Georgios Papachristou 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | from jarvis.skills.collection.activation import ActivationSkills 24 | from jarvis.skills.collection.info import AssistantInfoSkills 25 | from jarvis.skills.collection.datetime import DatetimeSkills 26 | from jarvis.skills.collection.browser import BrowserSkills 27 | from jarvis.skills.collection.general import UtilSkills 28 | from jarvis.skills.collection.internet import InternetSkills 29 | from jarvis.skills.collection.libreoffice import LibreofficeSkills 30 | from jarvis.skills.collection.linux import LinuxAppSkills 31 | from jarvis.skills.collection.location import LocationSkill 32 | from jarvis.skills.collection.reminder import ReminderSkills 33 | from jarvis.skills.collection.system_health import SystemHealthSkills 34 | from jarvis.skills.collection.weather import WeatherSkills 35 | from jarvis.skills.collection.text import WordSkills 36 | from jarvis.skills.collection.history import HistorySkills 37 | from jarvis.skills.collection.remember import RememberSkills 38 | from jarvis.skills.collection.math import MathSkills 39 | from jarvis.utils.mapping import math_tags 40 | from jarvis.skills.collection.configuration import ConfigurationSkills 41 | 42 | # All available assistant skills 43 | # Keys description: 44 | # - 'enable': boolean (With True are the enabled skills) 45 | # - 'func': The skill method in Skills 46 | # - 'tags': The available triggering tags 47 | # - 'description': skill description 48 | 49 | CONTROL_SKILLS = [ 50 | { 51 | 'func': ActivationSkills.assistant_greeting, 52 | 'tags': 'start, hi, hello, start, wake up', 53 | 'description': 'Enables the assistant (ready to hear command)' 54 | }, 55 | 56 | { 57 | 'func': ActivationSkills.disable_assistant, 58 | 'tags': 'bye, shut down, exit, termination', 59 | 'description': 'Stops the assistant service (disable assistant)' 60 | } 61 | ] 62 | 63 | BASIC_SKILLS = [ 64 | 65 | { 66 | 'enable': True, 67 | 'func': BrowserSkills.open_website_in_browser, 68 | 'tags': 'open', 69 | 'description': 'Opens a domain in browser' 70 | }, 71 | 72 | { 73 | 'enable': True, 74 | 'func': BrowserSkills.tell_me_today_news, 75 | 'tags': 'news, today news', 76 | 'description': 'Tells the daily news (find on Google newsfeed)' 77 | }, 78 | 79 | { 80 | 'enable': True, 81 | 'func': DatetimeSkills.tell_the_time, 82 | 'tags': 'time, hour', 83 | 'description': 'Tells the current time' 84 | }, 85 | 86 | { 87 | 'enable': True, 88 | 'func': DatetimeSkills.tell_the_date, 89 | 'tags': 'date', 90 | 'description': 'Tells the current date' 91 | }, 92 | 93 | { 94 | 'enable': True, 95 | 'func': BrowserSkills.tell_me_about, 96 | 'tags': 'search', 97 | 'description': 'Tells about something based on Google search' 98 | }, 99 | 100 | { 101 | 'enable': True, 102 | 'func': UtilSkills.speech_interruption, 103 | 'tags': 'stop', 104 | 'description': 'Stop/interrupt assistant speech' 105 | }, 106 | 107 | { 108 | 'enable': True, 109 | 'func': AssistantInfoSkills.assistant_help, 110 | 'tags': 'help', 111 | 'description': 'A list with all the available skills' 112 | }, 113 | 114 | { 115 | 'enable': True, 116 | 'func': WeatherSkills.tell_the_weather, 117 | 'tags': 'weather, temperature, weather prediction', 118 | 'description': 'Tells the weather for a location (default in current location)' 119 | }, 120 | 121 | { 122 | 'enable': True, 123 | 'func': AssistantInfoSkills.assistant_check, 124 | 'tags': 'hey, hi', 125 | 'description': 'User check if assistant works' 126 | }, 127 | 128 | { 129 | 'enable': True, 130 | 'func': LibreofficeSkills.open_libreoffice_calc, 131 | 'tags': 'calc, excel', 132 | 'description': 'Opens excel applcation' 133 | }, 134 | 135 | { 136 | 'enable': True, 137 | 'func': LibreofficeSkills.open_libreoffice_writer, 138 | 'tags': 'writer, word', 139 | 'description': 'Opens writer application' 140 | }, 141 | 142 | { 143 | 'enable': True, 144 | 'func': LibreofficeSkills.open_libreoffice_impress, 145 | 'tags': 'impress', 146 | 'description': 'Opens impress application' 147 | }, 148 | 149 | { 150 | 'enable': True, 151 | 'func': SystemHealthSkills.tell_memory_consumption, 152 | 'tags': 'ram, ram usage, memory, memory consumption', 153 | 'description': 'The assistant current memory consumption, ' 154 | 155 | }, 156 | 157 | { 158 | 'enable': True, 159 | 'func': BrowserSkills.open_in_youtube, 160 | 'tags': 'play', 161 | 'description': 'Plays music in Youtube' 162 | }, 163 | 164 | { 165 | 'enable': True, 166 | 'func': InternetSkills.run_speedtest, 167 | 'tags': 'speedtest, internet speed, ping', 168 | 'description': 'Checks internet speed' 169 | }, 170 | 171 | { 172 | 'enable': True, 173 | 'func': InternetSkills.internet_availability, 174 | 'tags': 'internet conection', 175 | 'description': 'Checks for internet availability' 176 | }, 177 | 178 | { 179 | 'enable': True, 180 | 'func': WordSkills.spell_a_word, 181 | 'tags': 'spell, spell the word', 182 | 'description': 'Spells a word' 183 | }, 184 | 185 | { 186 | 'enable': True, 187 | 'func': ReminderSkills.create_reminder, 188 | 'tags': 'reminder', 189 | 'description': 'Create a time reminder' 190 | }, 191 | 192 | { 193 | 'enable': True, 194 | 'func': AssistantInfoSkills.tell_the_skills, 195 | 'tags': 'skills, your skills, what are your skills', 196 | 'description': 'Tells all assistant available skills' 197 | }, 198 | 199 | { 200 | 'enable': True, 201 | 'func': LinuxAppSkills.open_note_app, 202 | 'tags': 'note', 203 | 'description': 'Ask to create a note' 204 | }, 205 | 206 | { 207 | 'enable': True, 208 | 'func': LinuxAppSkills.open_new_browser_window, 209 | 'tags': 'firefox, open firefox', 210 | 'description': 'Ask to open new browser window' 211 | }, 212 | 213 | { 214 | 'enable': True, 215 | 'func': LinuxAppSkills.open_new_bash, 216 | 'tags': 'bash', 217 | 'description': 'Ask to open new bash' 218 | }, 219 | 220 | { 221 | 'enable': True, 222 | 'func': LocationSkill.get_current_location, 223 | 'tags': 'my location, current location', 224 | 'description': 'Ask to tell you your current location' 225 | }, 226 | 227 | { 228 | 'enable': True, 229 | 'func': HistorySkills.show_history_log, 230 | 'tags': 'history, history log, user history', 231 | 'description': 'Ask to tell you asked commands' 232 | }, 233 | 234 | { 235 | 'enable': True, 236 | 'func': RememberSkills.remember, 237 | 'tags': 'remember', 238 | 'description': 'Remember question - answer pairs' 239 | }, 240 | 241 | { 242 | 'enable': True, 243 | 'func': RememberSkills.tell_response, 244 | 'tags': '', 245 | 'description': 'Util skill, there is no tags to call it' 246 | }, 247 | 248 | { 249 | 'enable': True, 250 | 'func': RememberSkills.clear_learned_skills, 251 | 'tags': 'clear learned skills, drop learned skills, remove learned skills', 252 | 'description': 'Clear the learned skills' 253 | }, 254 | 255 | { 256 | 'enable': True, 257 | 'func': UtilSkills.clear_console, 258 | 'tags': 'clear, clear console', 259 | 'description': 'Clears bash console' 260 | }, 261 | 262 | { 263 | 'enable': True, 264 | 'func': ReminderSkills.set_alarm, 265 | 'tags': 'alarm, set alarm', 266 | 'description': 'Set daily alarm (the assistant service should be running)' 267 | }, 268 | 269 | { 270 | 'enable': True, 271 | 'func': MathSkills.do_calculations, 272 | 'tags': math_tags, 273 | 'description': 'Do basic math calculations in bash terminal e.g " (5+5) ^ 2"' 274 | }, 275 | 276 | { 277 | 'enable': True, 278 | 'func': ConfigurationSkills.configure_assistant, 279 | 'tags': 'configure, change settings', 280 | 'description': 'Change the assistant setting values' 281 | }, 282 | 283 | { 284 | 'enable': True, 285 | 'func': UtilSkills.increase_master_volume, 286 | 'tags': 'increase volume, volume up, speak louder', 287 | 'description': 'Increases the speakers master volume' 288 | }, 289 | 290 | { 291 | 'enable': True, 292 | 'func': UtilSkills.reduce_master_volume, 293 | 'tags': 'reduce volume, volume down', 294 | 'description': 'Decreases the speakers master volume' 295 | }, 296 | 297 | { 298 | 'enable': True, 299 | 'func': UtilSkills.mute_master_volume, 300 | 'tags': 'mute', 301 | 'description': 'Mutes the speakers master volume' 302 | }, 303 | 304 | { 305 | 'enable': True, 306 | 'func': UtilSkills.max_master_volume, 307 | 'tags': 'volume max', 308 | 'description': 'Set max the speakers master volume' 309 | }, 310 | 311 | ] 312 | 313 | # Add name key in both BASIC_SKILLS and CONTROL_SKILLS 314 | for skill in BASIC_SKILLS + CONTROL_SKILLS: 315 | skill['name'] = skill['func'].__name__ 316 | 317 | skill_objects = {skill['func'].__name__: skill['func'] for skill in BASIC_SKILLS + CONTROL_SKILLS} 318 | 319 | 320 | def _convert_skill_object_to_str(skill): 321 | for sk in skill: 322 | sk.update((k, v.__name__) for k, v in sk.items() if k == 'func') 323 | 324 | 325 | _convert_skill_object_to_str(CONTROL_SKILLS) 326 | 327 | # Create enable basic skills 328 | ENABLED_BASIC_SKILLS = [skill for skill in BASIC_SKILLS if skill['enable']] 329 | _convert_skill_object_to_str(ENABLED_BASIC_SKILLS) 330 | --------------------------------------------------------------------------------