diff --git a/src/AI.py b/src/AI.py new file mode 100644 index 0000000..c80ac4b --- /dev/null +++ b/src/AI.py @@ -0,0 +1,47 @@ +import requests + +class AIResponse: + def __init__(self, api_key): + self.api_key = api_key + + def get_ai_answer(self, question): + url = 'https://api.aimlapi.com/v1/chat/completions' + headers = { + 'Authorization': f'Bearer {self.api_key}', + 'Content-Type': 'application/json' + } + payload = { + 'text': question + } + + try: + response = requests.post(url, headers=headers, json=payload) + response.raise_for_status() # will raise an error for 4xx/5xx responses + result = response.json() + return result.get('answer', 'AI could not provide an answer.') + except requests.exceptions.HTTPError as http_err: + print(f"HTTP error occurred: {http_err}") + return 'AI API error: Unable to get a response.' + except Exception as err: + print(f"Other error occurred: {err}") + return 'AI API error: Unable to get a response.' + + + + + + + + + + + + + + + + + + # pydevcasts@gmail.com + # Poing1981@ + # https://meet.google.com/ikb-byyj-bui \ No newline at end of file diff --git a/src/auth.py b/src/auth.py new file mode 100644 index 0000000..015ee27 --- /dev/null +++ b/src/auth.py @@ -0,0 +1,27 @@ +# auth.py +import time +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC + +class Authenticator: + def __init__(self, driver, email, password): + self.driver = driver + self.email = email + self.password = password + + def login(self): + try: + self.driver.get('https://accounts.google.com/ServiceLogin') + self.driver.find_element(By.ID, "identifierId").send_keys(self.email) + self.driver.find_element(By.ID, "identifierNext").click() + + password_field = WebDriverWait(self.driver, 10).until( + EC.element_to_be_clickable((By.XPATH, '//*[@id="password"]/div[1]/div/div[1]/input'))) + password_field.send_keys(self.password) + + WebDriverWait(self.driver, 10).until(EC.url_contains("google.com")) + print("Login successful!") + except Exception as e: + print(f"Error during login: {e}") + self.driver.quit() diff --git a/src/controls.py b/src/controls.py new file mode 100644 index 0000000..0ea3526 --- /dev/null +++ b/src/controls.py @@ -0,0 +1,22 @@ +# controls.py +import time +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.common.exceptions import TimeoutException + +class MeetControls: + def __init__(self, driver): + self.driver = driver + + def turn_off_mic_cam(self): + try: + time.sleep(5) + mic_button = WebDriverWait(self.driver, 10).until( + EC.element_to_be_clickable((By.XPATH, '//*[contains(concat(" ", @class, " "), concat(" ", "crqnQb", " "))]'))) + mic_button.click() + print("Microphone turned off.") + except TimeoutException: + print("TimeoutException: Element not found or not clickable within specified time.") + except Exception as e: + print(f"An error occurred while turning off mic/cam: {e}") diff --git a/src/driver_setup.py b/src/driver_setup.py new file mode 100644 index 0000000..2cbf586 --- /dev/null +++ b/src/driver_setup.py @@ -0,0 +1,21 @@ +# driver_setup.py +from selenium import webdriver +from selenium.webdriver.chrome.service import Service +from selenium.webdriver.chrome.options import Options + +def setup_driver(): + options = Options() + options.add_argument('--disable-blink-features=AutomationControlled') + options.add_argument('--start-maximized') + options.add_experimental_option("prefs", { + "profile.default_content_setting_values.media_stream_mic": 1, + "profile.default_content_setting_values.media_stream_camera": 1, + "profile.default_content_setting_values.geolocation": 0, + "profile.default_content_setting_values.notifications": 1 + }) + + # You can also specify the path to your ChromeDriver if needed + # service = Service('/path/to/chromedriver') + # return webdriver.Chrome(service=service, options=options) + + return webdriver.Chrome(options=options) diff --git a/src/google-meet-icon.jpg b/src/google-meet-icon.jpg new file mode 100644 index 0000000..649b31d Binary files /dev/null and b/src/google-meet-icon.jpg differ diff --git a/src/google_meet_bot.py b/src/google_meet_bot.py new file mode 100644 index 0000000..08972e4 --- /dev/null +++ b/src/google_meet_bot.py @@ -0,0 +1,49 @@ +# google_meet_bot.py +import time +import threading +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.common.exceptions import TimeoutException +from driver_setup import setup_driver +from translator import TextTranslator +from question_checker import QuestionChecker +from AI import AIResponse +from auth import Authenticator # Import the Authenticator class +from controls import MeetControls # Import the MeetControls class +from subtitle_saver import SubtitleSaver # Import the SubtitleSaver class + +class GoogleMeetBot: + def __init__(self, email, password, api_key, ai_api_key, update_signal): + self.email = email + self.password = password + self.api_key = api_key + self.ai_response = AIResponse(ai_api_key) + self.driver = setup_driver() + self.translator = TextTranslator() + self.question_checker = QuestionChecker(api_key) + self.authenticator = Authenticator(self.driver, self.email, self.password) + self.controls = MeetControls(self.driver) # Initialize MeetControls + self.subtitle_saver = SubtitleSaver(self.driver, self.translator, self.question_checker, update_signal) + + def login(self): + self.authenticator.login() # Use the Authenticator class for login + + def turn_off_mic_cam(self): + self.controls.turn_off_mic_cam() # Use the MeetControls class for turning off mic/cam + + def start(self, meeting_link): + self.login() + self.driver.get(meeting_link) + self.turn_off_mic_cam() + + subtitle_thread = threading.Thread(target=self.subtitle_saver.save_subtitles, daemon=True) # Use SubtitleSaver + subtitle_thread.start() + + try: + while True: + time.sleep(1) + except KeyboardInterrupt: + print("Program terminated by user.") + finally: + self.driver.quit() diff --git a/src/main.py b/src/main.py new file mode 100644 index 0000000..f3ef657 --- /dev/null +++ b/src/main.py @@ -0,0 +1,106 @@ +import sys +import time +from PyQt6.QtWidgets import (QApplication, QWidget, QVBoxLayout, + QLineEdit, QPushButton, QTextEdit, QFileDialog) +from PyQt6.QtCore import QThread, pyqtSignal +import driver_setup +from google_meet_bot import GoogleMeetBot +from subtitle_saver import SubtitleSaver + +class MeetingThread(QThread): + update_output = pyqtSignal(str) + + def __init__(self, email, password, meeting_link, api_key, ai_api_key, translator, question_checker): + super().__init__() + self.email = email + self.password = password + self.meeting_link = meeting_link + self.api_key = api_key + self.ai_api_key = ai_api_key + self.driver = driver_setup + self.translator = translator + self.question_checker = question_checker + + def run(self): + self.update_output.emit("Starting the meeting...") + self.bot = GoogleMeetBot(self.email, self.password, self.api_key, self.ai_api_key, self.update_output) + self.bot.start(self.meeting_link) + + # Sending update_output as update_signal + subtitle_saver = SubtitleSaver(self.driver, self.translator, self.question_checker, self.update_output) + subtitle_saver.save_subtitles() # This method runs concurrently + +class MainWindow(QWidget): + def __init__(self): + super().__init__() + self.setWindowTitle("Google Meet Bot") + self.setGeometry(400, 400, 800, 800) + + # Assume you initialize translator and question_checker here + self.translator = None # Replace with actual translator + self.question_checker = None # Replace with actual question_checker + + layout = QVBoxLayout() + + self.email_input = QLineEdit(self) + self.email_input.setPlaceholderText("Enter your email") + self.email_input.setFixedHeight(40) # Increased height + layout.addWidget(self.email_input) + + self.password_input = QLineEdit(self) + self.password_input.setPlaceholderText("Enter your password") + self.password_input.setEchoMode(QLineEdit.EchoMode.Password) + self.password_input.setFixedHeight(40) # Increased height + layout.addWidget(self.password_input) + + self.meeting_link_input = QLineEdit(self) + self.meeting_link_input.setPlaceholderText("Enter meeting link") + self.meeting_link_input.setFixedHeight(40) # Increased height + layout.addWidget(self.meeting_link_input) + + self.start_button = QPushButton("Start Meeting", self) + self.start_button.setStyleSheet("background-color: blue; font-size: 16px;") # Change button color + self.start_button.setFixedHeight(40) # In + self.start_button.clicked.connect(self.start_meeting) + layout.addWidget(self.start_button) + + self.output_area = QTextEdit(self) + self.output_area.setReadOnly(True) + self.output_area.setFixedHeight(400) # Set a fixed height for output area + layout.addWidget(self.output_area) + + self.download_button = QPushButton("Download Text File", self) + self.download_button.setStyleSheet("background-color: green; font-size: 16px;") # Change button color + self.download_button.setFixedHeight(40) # In + self.download_button.clicked.connect(self.download_file) + layout.addWidget(self.download_button) + + self.setLayout(layout) + + def start_meeting(self): + email = self.email_input.text() + password = self.password_input.text() + meeting_link = self.meeting_link_input.text() + + if email and password and meeting_link: + self.meeting_thread = MeetingThread(email, password, meeting_link, 'YOUR_API_KEY', 'YOUR_AI_API_KEY', self.translator, self.question_checker) + self.meeting_thread.update_output.connect(self.append_output) + self.meeting_thread.start() + else: + self.output_area.append("Please fill in all fields.") + + def append_output(self, message): + self.output_area.append(message) + + def download_file(self): + file_name, _ = QFileDialog.getSaveFileName(self, "Save File", "", "Text Files (*.txt);;All Files (*)") + if file_name: + with open(file_name, 'w', encoding='utf-8') as file: + file.write(self.output_area.toPlainText()) + self.output_area.append(f"File saved at {file_name}.") + +if __name__ == "__main__": + app = QApplication(sys.argv) + window = MainWindow() + window.show() + sys.exit(app.exec()) diff --git a/src/question_checker.py b/src/question_checker.py new file mode 100644 index 0000000..fede79c --- /dev/null +++ b/src/question_checker.py @@ -0,0 +1,32 @@ +# question_checker.py +import requests + +class QuestionChecker: + def __init__(self, api_key): + self.api_key = api_key + + def is_question(self, text): + wh_question_detected = self.contains_wh_question(text) + if wh_question_detected: + return True + + url = 'https://api.aimlapi.com/v1/chat/completions' # Replace with your API endpoint + headers = { + 'Authorization': f'Bearer {self.api_key}', + 'Content-Type': 'application/json' + } + payload = { + 'text': text + } + response = requests.post(url, headers=headers, json=payload) + if response.status_code == 200: + result = response.json() + return result.get('is_question', False) + else: + print(f"API error: {response.status_code}, Message: {response.text}") + return False + + def contains_wh_question(self, text): + wh_words = ['who', 'what', 'where', 'when', 'why', 'which'] + text_lower = text.lower() + return any(wh in text_lower for wh in wh_words) diff --git a/src/subtitle_saver.py b/src/subtitle_saver.py new file mode 100644 index 0000000..04fd70a --- /dev/null +++ b/src/subtitle_saver.py @@ -0,0 +1,44 @@ +# subtitle_saver.py +import time +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.common.exceptions import TimeoutException + +class SubtitleSaver: + def __init__(self, driver, translator, question_checker, update_signal): + self.driver = driver + self.translator = translator + self.question_checker = question_checker + self.previous_subtitles = set() + self.update_signal = update_signal + + def save_subtitles(self): + with open('subtitles.txt', 'a', encoding='utf-8') as f: + while True: + try: + subtitles = WebDriverWait(self.driver, 10).until( + EC.presence_of_element_located((By.XPATH, '//*[contains(concat(" ", @class, " "), concat(" ", "iOzk7", " "))]'))) + subtitle_text = subtitles.text.strip() + + if subtitle_text and subtitle_text not in self.previous_subtitles: + f.write("English: " + subtitle_text + '\n' + "*" * 20 + '\n') + + if self.question_checker.is_question(subtitle_text): + f.write("Type: Question" + '\n' + "*" * 20 + '\n') + else: + f.write("Type: Statement" + '\n' + "*" * 20 + '\n') + + translated_text = self.translator.translate(subtitle_text, dest='fa') + f.write("Persian: " + translated_text + '\n' + "*" * 20 + '\n') + + f.flush() + self.update_signal.emit(f"English: {subtitle_text}\nType: {'Question' if self.question_checker.is_question(subtitle_text) else 'Statement'}\nPersian: {translated_text}") + + self.previous_subtitles.add(subtitle_text) + time.sleep(1) + except TimeoutException: + print("TimeoutException: Element not found.") + except Exception as e: + print(f"Error: {e}") + break diff --git a/src/test.py b/src/test.py new file mode 100644 index 0000000..f81ac8a --- /dev/null +++ b/src/test.py @@ -0,0 +1,68 @@ +import unittest +from unittest.mock import patch, MagicMock +from google_meet_bot import GoogleMeetBot + +class TestGoogleMeetBot(unittest.TestCase): + + @patch('google_meet_bot.setup_driver') + @patch('google_meet_bot.Authenticator') + @patch('google_meet_bot.MeetControls') + @patch('google_meet_bot.SubtitleSaver') + @patch('google_meet_bot.TextTranslator') + @patch('google_meet_bot.QuestionChecker') + @patch('google_meet_bot.AIResponse') + def setUp(self, mock_ai_response, mock_question_checker, mock_text_translator, + mock_subtitle_saver, mock_meet_controls, mock_authenticator, + mock_setup_driver): + # Mocking the setup for GoogleMeetBot + self.mock_driver = MagicMock() + mock_setup_driver.return_value = self.mock_driver + self.bot = GoogleMeetBot( + email='test@example.com', + password='password123', + api_key='fake_api_key', + ai_api_key='fake_ai_api_key', + update_signal=MagicMock() + ) + self.bot.authenticator = mock_authenticator.return_value + self.bot.controls = mock_meet_controls.return_value + self.bot.subtitle_saver = mock_subtitle_saver.return_value + self.bot.translator = mock_text_translator.return_value + self.bot.question_checker = mock_question_checker.return_value + self.bot.ai_response = mock_ai_response.return_value + + def test_login(self): + # Test that the login method calls the Authenticator's login method + self.bot.login() + self.bot.authenticator.login.assert_called_once() + + def test_turn_off_mic_cam(self): + # Test that the turn_off_mic_cam method calls the MeetControls method + self.bot.turn_off_mic_cam() + self.bot.controls.turn_off_mic_cam.assert_called_once() + + @patch('google_meet_bot.time.sleep', return_value=None) # Mock sleep to avoid delays + def test_start_meeting(self, mock_sleep): + meeting_link = "https://meet.google.com/test-meeting" + self.bot.start(meeting_link) + + # Check that the login method is called + self.bot.authenticator.login.assert_called_once() + + # Check that the driver navigates to the meeting link + self.bot.driver.get.assert_called_with(meeting_link) + + # Check that the microphone and camera are turned off + self.bot.controls.turn_off_mic_cam.assert_called_once() + + # Check that the subtitle saver thread is started + self.assertTrue(self.bot.subtitle_saver.save_subtitles.called) + + def test_program_termination(self): + # Test that the driver quits when the program is terminated + with self.assertRaises(KeyboardInterrupt): + self.bot.start("https://meet.google.com/test-meeting") + self.bot.driver.quit.assert_called_once() + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/src/translator.py b/src/translator.py new file mode 100644 index 0000000..f67b804 --- /dev/null +++ b/src/translator.py @@ -0,0 +1,9 @@ +# translator.py +from googletrans import Translator + +class TextTranslator: + def __init__(self): + self.translator = Translator() + + def translate(self, text, dest='fa'): + return self.translator.translate(text, dest=dest).text