From 65f0761d11badc370b93cb0c6d148239a4ff9422 Mon Sep 17 00:00:00 2001 From: Michael Beaudoin <112587549+mbeaudoin24@users.noreply.github.com> Date: Sun, 21 Apr 2024 18:51:31 -0400 Subject: [PATCH 1/6] Create passGen.py --- jarviscli/plugins/passGen.py | 55 ++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 jarviscli/plugins/passGen.py diff --git a/jarviscli/plugins/passGen.py b/jarviscli/plugins/passGen.py new file mode 100644 index 000000000..e2388e1cd --- /dev/null +++ b/jarviscli/plugins/passGen.py @@ -0,0 +1,55 @@ +import secrets +import string +from plugin import plugin, require, alias + +@require(network=False) +@alias("generate password") +@plugin("password generator") +class PasswordGenerator: + """ + This plugin generates a strong, random password with a specified length and choice of character types: + uppercase, lowercase, digits, and special characters. + """ + + def __call__(self, jarvis, s): + self.generate_password(jarvis) + + def generate_password(self, jarvis): + jarvis.say("Welcome to the Password Generator!") + length = jarvis.input_number("Enter the desired password length (minimum 12): ", rtype=float) + if length is None or length < 12: + jarvis.say("Invalid length. Password length must be at least 12 characters for security.") + return + + include_uppercase = jarvis.ask_yes_no("Include uppercase letters (A-Z)? (yes/no): ") + include_lowercase = jarvis.ask_yes_no("Include lowercase letters (a-z)? (yes/no): ") + include_digits = jarvis.ask_yes_no("Include digits (0-9)? (yes/no): ") + include_specials = jarvis.ask_yes_no("Include special characters (e.g., @#$%)? (yes/no): ") + + if not any([include_uppercase, include_lowercase, include_digits, include_specials]): + jarvis.say("At least one character type must be selected!") + return + + password = self.create_password(int(length), include_uppercase, include_lowercase, include_digits, include_specials) + jarvis.say(f"Generated Password: {password}", color='green') + + def create_password(self, length, include_uppercase, include_lowercase, include_digits, include_specials): + char_pool = [] + if include_uppercase: + char_pool.append(string.ascii_uppercase) + if include_lowercase: + char_pool.append(string.ascii_lowercase) + if include_digits: + char_pool.append(string.digits) + if include_specials: + char_pool.append(string.punctuation) + + if not char_pool: + return '' + + # Ensuring each character type selected is used at least once + password_chars = [secrets.choice(chars) for chars in char_pool] + password_chars += [secrets.choice(''.join(char_pool)) for _ in range(length - len(password_chars))] + + secrets.SystemRandom().shuffle(password_chars) # Use SystemRandom for better entropy + return ''.join(password_chars) From 7e45f713620b59b72f5cc7638e855740e16fb34f Mon Sep 17 00:00:00 2001 From: Michael Beaudoin <112587549+mbeaudoin24@users.noreply.github.com> Date: Sun, 21 Apr 2024 18:52:03 -0400 Subject: [PATCH 2/6] Create soccerApi.py --- jarviscli/plugins/soccerApi.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 jarviscli/plugins/soccerApi.py diff --git a/jarviscli/plugins/soccerApi.py b/jarviscli/plugins/soccerApi.py new file mode 100644 index 000000000..9e74a269e --- /dev/null +++ b/jarviscli/plugins/soccerApi.py @@ -0,0 +1,31 @@ +import requests +from plugin import plugin, alias, require +from colorama import Fore + +@require(network=True) +@alias("soccer") +@plugin("soccer scores") +class SoccerScores: + """The user can request the latest soccer scores, and the API from TheSportsDB will be used to make a GET request and + fetch data. Then, the user can see the latest scores from major soccer games.""" + + def __call__(self, jarvis: "JarvisAPI", s: str) -> None: + self.print_latest_scores(jarvis) + + def print_latest_scores(self, jarvis: "JarvisAPI"): + jarvis.say("Fetching the latest soccer scores...") + try: + response = requests.get('https://www.thesportsdb.com/api/v1/json/3/latestsoccer.php') + response.raise_for_status() + data = response.json() + + if 'games' in data: + for game in data['games']: + jarvis.say(f"Match: {game['strEvent']}", color=Fore.BLUE) + jarvis.say(f"Date: {game['dateEvent']}") + jarvis.say(f"Score: {game['intHomeScore']} - {game['intAwayScore']}", color=Fore.GREEN) + jarvis.say(f"Status: {game['strStatus']}\n") + else: + jarvis.say("No recent games data found.", color=Fore.RED) + except requests.RequestException as e: + jarvis.say(f"An error occurred while fetching data: {e}", color=Fore.RED) From 2e5e4976b39a5954b00550fff467e337b27b3ec3 Mon Sep 17 00:00:00 2001 From: Michael Beaudoin <112587549+mbeaudoin24@users.noreply.github.com> Date: Sun, 21 Apr 2024 18:54:48 -0400 Subject: [PATCH 3/6] Create test_passGen.py --- jarviscli/tests/test_passGen.py | 58 +++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 jarviscli/tests/test_passGen.py diff --git a/jarviscli/tests/test_passGen.py b/jarviscli/tests/test_passGen.py new file mode 100644 index 000000000..771df652b --- /dev/null +++ b/jarviscli/tests/test_passGen.py @@ -0,0 +1,58 @@ +import unittest +from unittest.mock import patch, MagicMock +from your_module import PasswordGenerator, JarvisAPI # Adjust import according to your project structure + +class TestPasswordGenerator(unittest.TestCase): + def setUp(self): + self.jarvis = MagicMock(spec=JarvisAPI) + self.jarvis.input_number = MagicMock() + self.jarvis.ask_yes_no = MagicMock() + self.password_generator = PasswordGenerator() + + def mock_inputs(self, length, upper, lower, digits, specials): + self.jarvis.input_number.return_value = length + self.jarvis.ask_yes_no.side_effect = [upper, lower, digits, specials] + + @patch('secrets.choice') + @patch('secrets.SystemRandom.shuffle') + def test_valid_password_generation(self, mock_shuffle, mock_choice): + self.mock_inputs(12, 'yes', 'yes', 'yes', 'yes') + mock_choice.side_effect = lambda x: x[0] # Always choose the first character of the pool for predictability in tests + + self.password_generator(self.jarvis, '') + self.jarvis.say.assert_called_with('Generated Password: Aa0@Aa0@Aa0@', color='green') + + def test_password_length_below_minimum(self): + self.mock_inputs(8, 'yes', 'yes', 'yes', 'yes') + + self.password_generator(self.jarvis, '') + self.jarvis.say.assert_called_with('Invalid length. Password length must be at least 12 characters for security.') + + def test_no_character_types_selected(self): + self.mock_inputs(12, 'no', 'no', 'no', 'no') + + self.password_generator(self.jarvis, '') + self.jarvis.say.assert_called_with('At least one character type must be selected!') + + @patch('secrets.choice') + @patch('secrets.SystemRandom.shuffle') + def test_ensuring_diversity_in_characters(self, mock_shuffle, mock_choice): + # Using a simple deterministic pattern for secrets.choice + mock_choice.side_effect = lambda pool: pool[0] # Always select the first character + self.mock_inputs(12, 'yes', 'yes', 'yes', 'yes') + + self.password_generator(self.jarvis, '') + expected_call = 'Generated Password: Aa0@Aa0@Aa0@' + self.jarvis.say.assert_called_with(expected_call, color='green') + # Check that each type of character was indeed included + self.assertTrue(all(x in expected_call for x in 'Aa0@')) + + @patch('secrets.choice') + @patch('secrets.SystemRandom.shuffle') + def test_different_combinations_of_character_types(self, mock_shuffle, mock_choice): + # Combination of upper and digits only + self.mock_inputs(12, 'yes', 'no', 'yes', 'no') + mock_choice.side_effect = lambda pool: pool[0] # Simplistic choice + self.password_generator(self.jarvis, '') + expected_call = 'Generated Password: A0A0A0A0A0A0' + self.jarvis.say.assert_called_with(expected_call, color='green') From a29d6d0013abdee73a67f3f67de13042e9fa5bd5 Mon Sep 17 00:00:00 2001 From: Michael Beaudoin <112587549+mbeaudoin24@users.noreply.github.com> Date: Sun, 21 Apr 2024 18:55:16 -0400 Subject: [PATCH 4/6] Create test_soccerApi.py --- jarviscli/tests/test_soccerApi.py | 59 +++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 jarviscli/tests/test_soccerApi.py diff --git a/jarviscli/tests/test_soccerApi.py b/jarviscli/tests/test_soccerApi.py new file mode 100644 index 000000000..761b2b271 --- /dev/null +++ b/jarviscli/tests/test_soccerApi.py @@ -0,0 +1,59 @@ +import unittest +from unittest.mock import patch, Mock +import requests +from your_module import SoccerScores, JarvisAPI # Adjust import according to your project structure + +class TestSoccerScores(unittest.TestCase): + def setUp(self): + # Setup a mock for the JarvisAPI which would be passed to the plugin + self.jarvis = Mock() + self.jarvis.say = Mock() + + # Instantiate the SoccerScores class + self.plugin = SoccerScores() + + @patch('requests.get') + def test_successful_response(self, mock_get): + # Mock the JSON response to simulate the API returning valid game data + mock_get.return_value = Mock(ok=True) + mock_get.return_value.json.return_value = { + "games": [ + {"strEvent": "Team A vs Team B", "dateEvent": "2024-04-16", "intHomeScore": "2", "intAwayScore": "1", "strStatus": "Finished"} + ] + } + mock_get.return_value.raise_for_status = Mock() + + self.plugin.print_latest_scores(self.jarvis) + self.jarvis.say.assert_any_call("Fetching the latest soccer scores...") + self.jarvis.say.assert_any_call("Match: Team A vs Team B", color=Fore.BLUE) + self.jarvis.say.assert_any_call("Score: 2 - 1", color=Fore.GREEN) + + @patch('requests.get') + def test_no_games_found(self, mock_get): + # Simulate the API returning valid response but no games + mock_get.return_value = Mock(ok=True) + mock_get.return_value.json.return_value = {"games": []} + mock_get.return_value.raise_for_status = Mock() + + self.plugin.print_latest_scores(self.jarvis) + self.jarvis.say.assert_called_with("No recent games data found.", color=Fore.RED) + + @patch('requests.get') + def test_api_error(self, mock_get): + # Simulate an API error + mock_get.side_effect = requests.exceptions.RequestException("API Error") + + self.plugin.print_latest_scores(self.jarvis) + self.jarvis.say.assert_called_with("An error occurred while fetching data: API Error", color=Fore.RED) + + @patch('requests.get') + def test_invalid_json_response(self, mock_get): + # Simulate invalid JSON response + mock_get.return_value = Mock(ok=True) + mock_get.return_value.json.side_effect = ValueError("Invalid JSON") + + self.plugin.print_latest_scores(self.jarvis) + self.jarvis.say.assert_called_with("An error occurred while fetching data: Invalid JSON", color=Fore.RED) + +if __name__ == '__main__': + unittest.main() From a8a0b232bcdc403ea38865314608579080d360c0 Mon Sep 17 00:00:00 2001 From: Michael Beaudoin <112587549+mbeaudoin24@users.noreply.github.com> Date: Sun, 21 Apr 2024 19:01:07 -0400 Subject: [PATCH 5/6] Create passGen_documentation.md --- jarviscli/plugins/passGen_documentation.md | 55 ++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 jarviscli/plugins/passGen_documentation.md diff --git a/jarviscli/plugins/passGen_documentation.md b/jarviscli/plugins/passGen_documentation.md new file mode 100644 index 000000000..be00a1082 --- /dev/null +++ b/jarviscli/plugins/passGen_documentation.md @@ -0,0 +1,55 @@ +Documentation for passGen.py +Overview +passGen.py contains a class PasswordGenerator designed for Jarvis, a plugin-based command-line tool. This plugin allows users to generate strong, random passwords directly through the command line interface. It supports various customizable parameters such as password length and character types including uppercase, lowercase, digits, and special characters. + +Requirements +Python 3.6+ +The Jarvis command line interface setup +secrets and string libraries (standard in Python 3.6+) +Installation +No additional installation is necessary beyond setting up the Jarvis framework and ensuring that Python 3.6 or newer is installed on your system. Simply place passGen.py in the Jarvis plugins directory, typically found at jarviscli/plugins. + +Usage +To use the PasswordGenerator plugin, ensure it is loaded in the Jarvis framework, and initiate it through the Jarvis command line interface with the following command: + +generate password +This will activate the plugin and start the password generation process. + +Class and Methods +PasswordGenerator + +Purpose: Provides functionalities to generate a secure, random password based on user-specified criteria. +Methods: +__call__(self, jarvis, s): Entry point method called by the Jarvis framework when the plugin is activated. It delegates the main operation to generate_password. +generate_password(self, jarvis): Interacts with the user to specify password criteria and initiates the password creation process. +create_password(self, length, include_uppercase, include_lowercase, include_digits, include_specials): Constructs a password based on specified parameters ensuring randomness and security through cryptographic methods. +Detailed Method Description +generate_password(self, jarvis) + +Parameters: +jarvis: Instance of JarvisAPI, used to interact with the user. +Functionality: +Prompts the user for the desired password length and character types. +Validates the user inputs and ensures security standards are met. +Calls create_password to generate the password and outputs the result. +create_password(self, length, include_uppercase, include_lowercase, include_digits, include_specials) + +Parameters: +length (int): The desired length of the password. +include_uppercase (bool): Whether to include uppercase letters. +include_lowercase (bool): Whether to include lowercase letters. +include_digits (bool): Whether to include numeric digits. +include_specials (bool): Whether to include special characters. +Functionality: +Builds a character pool from the selected character types. +Ensures that each selected character type is represented at least once. +Randomly constructs the password from the character pool using cryptographically secure methods and shuffles it for additional randomness. +Returns the generated password. +Notes +This plugin uses the secrets module for secure random number generation, suitable for cryptographic use, ensuring that the generated passwords are both strong and secure. + +Example +To generate a password with all character types, 20 characters long: + +generate password +Follow the prompts to specify length and character inclusions. From 77422577b1dba3e4611ae564d28598c42f722008 Mon Sep 17 00:00:00 2001 From: Michael Beaudoin <112587549+mbeaudoin24@users.noreply.github.com> Date: Sun, 21 Apr 2024 19:09:49 -0400 Subject: [PATCH 6/6] Create soccerApi_documentation.md --- jarviscli/plugins/soccerApi_documentation.md | 40 ++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 jarviscli/plugins/soccerApi_documentation.md diff --git a/jarviscli/plugins/soccerApi_documentation.md b/jarviscli/plugins/soccerApi_documentation.md new file mode 100644 index 000000000..d768492fb --- /dev/null +++ b/jarviscli/plugins/soccerApi_documentation.md @@ -0,0 +1,40 @@ +Overview +The SoccerScores plugin is designed to fetch and display the latest soccer scores from major games using TheSportsDB API. This plugin is integrated into the Jarvis command-line interface, allowing users to get real-time soccer game updates directly from their terminal. + +Requirements +Python 3.x +The requests library installed (pip install requests) +The Jarvis command-line interface setup +An active internet connection (as this plugin requires network access) +Installation +This plugin should be placed in the Jarvis plugins directory. No additional installation steps are needed beyond ensuring that all dependencies (like the requests library) are installed. Here is how you can place it: + +Navigate to the Jarvis plugins directory (typically found at jarviscli/plugins). +Add the SoccerScores.py file to this directory. +Usage +To use the SoccerScores plugin within Jarvis, invoke the plugin through the command line: + +soccer +This command is set up via the @alias("soccer") decorator, making it easy to remember and use. + +Class and Methods +SoccerScores + +Purpose: Fetches and displays live soccer scores from TheSportsDB API. +Methods: +__call__(self, jarvis, s): This method is the entry point for the Jarvis plugin activation. It calls the print_latest_scores method. +print_latest_scores(self, jarvis): Handles the communication with TheSportsDB API, processes the JSON data, and outputs the soccer scores to the user. +Detailed Method Description +print_latest_scores(self, jarvis) + +Parameters: +jarvis: Instance of JarvisAPI, used to interact with the user. +Functionality: +Makes an HTTP GET request to TheSportsDB API to fetch the latest soccer scores. +Parses the JSON response and formats the output to display match details such as event, date, score, and status. +Handles potential network errors gracefully and informs the user if any issues occur or if no data is found. +Example +To check the latest soccer scores, simply type the following command in the Jarvis interface: + +soccer +Follow the interactive prompts if any, and view the latest scores as they are fetched and displayed.