From e33563fa75690ea6194155c1a59f6609963d7da5 Mon Sep 17 00:00:00 2001 From: Madhumitha Aravelli Date: Sat, 26 Oct 2024 13:45:31 -0400 Subject: [PATCH 1/3] Added voice recognition feature --- .idea/.gitignore | 8 ++++++++ .idea/DollarBot.iml | 15 +++++++++++++++ .idea/misc.xml | 4 ++++ .idea/modules.xml | 8 ++++++++ .idea/vcs.xml | 6 ++++++ code/code.py | 46 +++++++++++++++++++++++++++++++++++++++++++++ history.csv | 4 ++++ run.sh | 0 setup.sh | 0 user_limits.json | 6 ++++++ 10 files changed, 97 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/DollarBot.iml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 history.csv mode change 100644 => 100755 run.sh mode change 100644 => 100755 setup.sh create mode 100644 user_limits.json diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 000000000..13566b81b --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/DollarBot.iml b/.idea/DollarBot.iml new file mode 100644 index 000000000..6e7c09c28 --- /dev/null +++ b/.idea/DollarBot.iml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 000000000..3205808ec --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 000000000..0ed127fcd --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 000000000..35eb1ddfb --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/code/code.py b/code/code.py index 13b84473c..7134bc8c1 100644 --- a/code/code.py +++ b/code/code.py @@ -47,8 +47,12 @@ import monthly import sendEmail import add_recurring +import os +import tempfile +import speech_recognition as sr from datetime import datetime from jproperties import Properties +from pydub import AudioSegment configs = Properties() @@ -179,6 +183,48 @@ def command_weekly(message): """ weekly.run(message, bot) +@bot.message_handler(content_types=['voice']) +def handle_voice(message): + # Get the voice file + file_info = bot.get_file(message.voice.file_id) + downloaded_file = bot.download_file(file_info.file_path) + + # Create a temporary OGG file + with tempfile.NamedTemporaryFile(delete=False, suffix='.ogg') as temp_ogg: + temp_ogg.write(downloaded_file) + temp_ogg_path = temp_ogg.name + + # Convert OGG to WAV + temp_wav_path = tempfile.NamedTemporaryFile(delete=False, suffix='.wav').name + audio = AudioSegment.from_ogg(temp_ogg_path) + audio.export(temp_wav_path, format='wav') + + # Use SpeechRecognition to convert voice to text + recognizer = sr.Recognizer() + with sr.AudioFile(temp_wav_path) as source: + audio_data = recognizer.record(source) + try: + text = recognizer.recognize_google(audio_data) + process_command(text, message) + except sr.UnknownValueError: + bot.reply_to(message, "Sorry, I could not understand the audio.") + except sr.RequestError as e: + bot.reply_to(message, "Could not request results from the speech recognition service.") + + # Cleanup: remove the temporary files + os.remove(temp_ogg_path) + os.remove(temp_wav_path) + +def process_command(text, message): + if "expense" in text: + command_add(message) + elif "history" in text: + command_history(message) # Call the existing history command + elif "budget" in text: + command_budget(message) # Call the existing budget command + else: + bot.send_message(message.chat.id, "I didn't recognize that command.") + # defines how the /monthly command has to be handled/processed @bot.message_handler(commands=["monthly"]) def command_monthly(message): diff --git a/history.csv b/history.csv new file mode 100644 index 000000000..57398bb9c --- /dev/null +++ b/history.csv @@ -0,0 +1,4 @@ +Date,Category,Amount +02-Oct-2024,Food,$ 12 +12-Oct-2024,Groceries,$ 10.0 +26-Oct-2024,Food,$ 95.0 diff --git a/run.sh b/run.sh old mode 100644 new mode 100755 diff --git a/setup.sh b/setup.sh old mode 100644 new mode 100755 diff --git a/user_limits.json b/user_limits.json new file mode 100644 index 000000000..6bd7dde21 --- /dev/null +++ b/user_limits.json @@ -0,0 +1,6 @@ +{ + "6365998385": { + "food": 90.0, + "grocery": 70.0 + } +} \ No newline at end of file From 3481a95c9331887992987c8da6475998a9a448f1 Mon Sep 17 00:00:00 2001 From: Madhumitha Aravelli Date: Sat, 26 Oct 2024 18:26:29 -0400 Subject: [PATCH 2/3] Added test cases for handle_voice --- test/__init__.py | 0 test/test_voice.py | 72 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 test/__init__.py create mode 100644 test/test_voice.py diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/test/test_voice.py b/test/test_voice.py new file mode 100644 index 000000000..db0cb4fd6 --- /dev/null +++ b/test/test_voice.py @@ -0,0 +1,72 @@ +import unittest +from unittest.mock import patch, MagicMock +import tempfile +import os +from code import handle_voice, process_command + +class TestVoiceHandler(unittest.TestCase): + + @patch('bot.get_file') + @patch('bot.download_file') + @patch('tempfile.NamedTemporaryFile') + @patch('pydub.AudioSegment.from_ogg') + @patch('speech_recognition.Recognizer') + def test_handle_voice_success(self, MockRecognizer, MockAudioSegment, MockNamedTempFile, MockDownloadFile, MockGetFile): + # Mock file details and download + MockGetFile.return_value.file_path = 'fake_path.ogg' + MockDownloadFile.return_value = b'fake_ogg_data' + + # Mock tempfile behavior + temp_ogg = MagicMock() + temp_wav = MagicMock() + MockNamedTempFile.side_effect = [temp_ogg, temp_wav] + temp_ogg.name = 'temp.ogg' + temp_wav.name = 'temp.wav' + + # Mock audio conversion + MockAudioSegment.from_ogg.return_value.export = MagicMock() + + # Mock speech recognition + recognizer_instance = MockRecognizer.return_value + recognizer_instance.record.return_value = "fake_audio_data" + recognizer_instance.recognize_google.return_value = "this is a test expense" + + # Mock process_command function + with patch('process_command') as mock_process_command: + handle_voice(MagicMock()) # Simulate calling the voice handler + + # Assertions + MockGetFile.assert_called_once() + MockDownloadFile.assert_called_once() + MockAudioSegment.from_ogg.assert_called_once_with('temp.ogg') + recognizer_instance.recognize_google.assert_called_once() + mock_process_command.assert_called_once_with("this is a test expense", MagicMock()) + + # Cleanup mocks + os.remove('temp.ogg') + os.remove('temp.wav') + + @patch('bot.send_message') + def test_process_command(self, mock_send_message): + message = MagicMock() + + # Test different commands + with patch('command_add') as mock_command_add, \ + patch('command_history') as mock_command_history, \ + patch('command_budget') as mock_command_budget: + + process_command("add expense", message) + mock_command_add.assert_called_once_with(message) + + process_command("show history", message) + mock_command_history.assert_called_once_with(message) + + process_command("set budget", message) + mock_command_budget.assert_called_once_with(message) + + # Test unrecognized command + process_command("unknown command", message) + mock_send_message.assert_called_once_with(message.chat.id, "I didn't recognize that command.") + +if __name__ == '__main__': + unittest.main() From eab72539308ed8cab6b235559d63b12338be87f0 Mon Sep 17 00:00:00 2001 From: Madhumitha Aravelli Date: Sat, 26 Oct 2024 18:43:03 -0400 Subject: [PATCH 3/3] Added more commands that can handle voice --- code/code.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/code/code.py b/code/code.py index 7134bc8c1..475833c2f 100644 --- a/code/code.py +++ b/code/code.py @@ -205,6 +205,7 @@ def handle_voice(message): audio_data = recognizer.record(source) try: text = recognizer.recognize_google(audio_data) + bot.send_message(message.chat.id, f"I heard: \"{text}\"") process_command(text, message) except sr.UnknownValueError: bot.reply_to(message, "Sorry, I could not understand the audio.") @@ -222,6 +223,16 @@ def process_command(text, message): command_history(message) # Call the existing history command elif "budget" in text: command_budget(message) # Call the existing budget command + elif "menu" in text: + start_and_menu_command(message) + elif "help" in text: + show_help(message) + elif "weekly" in text: + command_weekly(message) + elif "monthly" in text: + command_monthly(message) + elif "predict" in text: + command_predict(message) else: bot.send_message(message.chat.id, "I didn't recognize that command.")