diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml new file mode 100644 index 0000000..7819196 --- /dev/null +++ b/.github/workflows/workflow.yml @@ -0,0 +1,31 @@ +name: CI + +on: + push: + branches: + - master # Replace with your main branch name if different + pull_request: + branches: + - master # This runs on pull requests targeting the main branch + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v3 + with: + python-version: '3.12' # You can specify the version of Python to use + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Run tests + run: | + pytest # Adjust this based on how you run your tests \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..19f681b --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2024 Siyamak Abbasnezhad, Mahan Shirsavar + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/chromedriver b/chromedriver deleted file mode 100755 index f498669..0000000 Binary files a/chromedriver and /dev/null differ diff --git a/google_meet_bot.py b/google_meet_bot.py deleted file mode 100644 index f833357..0000000 --- a/google_meet_bot.py +++ /dev/null @@ -1,49 +0,0 @@ -# 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_fa 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/main.py b/main.py deleted file mode 100644 index bbed29e..0000000 --- a/main.py +++ /dev/null @@ -1,254 +0,0 @@ -# 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_fr import SubtitleSaver_fr -# from subtitle_saver_fa import SubtitleSaver -# from subtitle_saver_ch import SubtitleSaver_ch -# 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 -# # Farsi -# subtitle_saver = SubtitleSaver(self.driver, self.translator, self.question_checker, self.update_output) -# subtitle_saver.save_subtitles() # This method runs concurrently -# # Franch -# subtitle_saver_fr = SubtitleSaver_fr(self.driver, self.translator, self.question_checker, self.update_output) -# subtitle_saver_fr.save_subtitles_fr() # This method runs concurrently -# # Chinese -# subtitle_saver_ch = SubtitleSaver_ch(self.driver, self.translator, self.question_checker, self.update_output) -# subtitle_saver_ch.save_subtitles_ch() # 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()) - - - - - -###############################################################3 - -import sys -from PyQt6.QtWidgets import (QApplication, QWidget, QVBoxLayout, - QLineEdit, QPushButton, QTextEdit, QFileDialog, QComboBox) -from PyQt6.QtCore import QThread, pyqtSignal -import driver_setup -from google_meet_bot import GoogleMeetBot -from subtitle_saver_fr import SubtitleSaver_fr -from subtitle_saver_fa import SubtitleSaver -from subtitle_saver_ch import SubtitleSaver_ch - -class MeetingThread(QThread): - update_output = pyqtSignal(str) - - def __init__(self, email, password, meeting_link, api_key, ai_api_key, translator, question_checker, selected_language): - 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 - self.selected_language = selected_language - - 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) - - # Subtitle saving based on selected language - if self.selected_language == "Farsi": - subtitle_saver = SubtitleSaver(self.driver, self.translator, self.question_checker, self.update_output) - subtitle_saver.save_subtitles() - elif self.selected_language == "French": - subtitle_saver_fr = SubtitleSaver_fr(self.driver, self.translator, self.question_checker, self.update_output) - subtitle_saver_fr.save_subtitles_fr() - elif self.selected_language == "Chinese": - subtitle_saver_ch = SubtitleSaver_ch(self.driver, self.translator, self.question_checker, self.update_output) - subtitle_saver_ch.save_subtitles_ch() - -class MainWindow(QWidget): - def __init__(self): - super().__init__() - self.setWindowTitle("Google Meet Bot") - self.setGeometry(400, 400, 800, 800) - - - -# 22@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - # Make sure to replace the placeholders for self.translator and self.question_checker with - # actual implementations or mock objects for testing. - - - # 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) - 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) - 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) - layout.addWidget(self.meeting_link_input) - - # Create a QComboBox for selecting subtitle language - self.language_combo = QComboBox(self) - self.language_combo.addItems(["Select Language", "Farsi", "French", "Chinese"]) - layout.addWidget(self.language_combo) - - self.start_button = QPushButton("Start Meeting", self) - self.start_button.setStyleSheet("background-color: blue; font-size: 16px;") - self.start_button.setFixedHeight(40) - 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) - layout.addWidget(self.output_area) - - self.download_button = QPushButton("Download Text File", self) - self.download_button.setStyleSheet("background-color: green; font-size: 16px;") - self.download_button.setFixedHeight(40) - 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() - selected_language = self.language_combo.currentText() - - if email and password and meeting_link and selected_language != "Select Language": - self.meeting_thread = MeetingThread(email, password, meeting_link, 'YOUR_API_KEY', 'YOUR_AI_API_KEY', self.translator, self.question_checker, selected_language) - self.meeting_thread.update_output.connect(self.append_output) - self.meeting_thread.start() - else: - self.output_area.append("Please fill in all fields and select a language.") - - 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/setup.py b/setup.py new file mode 100644 index 0000000..7e90440 --- /dev/null +++ b/setup.py @@ -0,0 +1,23 @@ +from setuptools import setup, find_packages + +setup( + name='MeetMa', # Package name in lowercase + version='0.0.1', + packages=find_packages(where='.'), # Automatically find packages + install_requires=[ + 'PyQt6', + 'beautifulsoup4', + 'googletrans', + 'requests', + 'selenium' + ], + entry_points={ + 'console_scripts': [ + 'meetma=src.main:main', # Adjust based on your actual structure + ], + }, + author='Siyamak Abasnezhad, Mahan Shirsavar', + author_email='pydevcasts@gmail.com', + description='Google Meet Bot is a Python application that automates participation in Google Meet meetings by extracting real-time subtitles, translating them, and detecting questions, enhancing users online meeting experience.', + url='https://github.com/pydevcasts/MeetMa', # Your GitHub URL +) \ No newline at end of file diff --git a/AI.py b/src/AI.py similarity index 100% rename from AI.py rename to src/AI.py diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..cfa541f --- /dev/null +++ b/src/__init__.py @@ -0,0 +1 @@ +from main import MainWindow \ No newline at end of file diff --git a/auth.py b/src/auth.py similarity index 100% rename from auth.py rename to src/auth.py diff --git a/controls.py b/src/controls.py similarity index 100% rename from controls.py rename to src/controls.py diff --git a/driver_setup.py b/src/driver_setup.py similarity index 100% rename from driver_setup.py rename to src/driver_setup.py diff --git a/google-meet-icon.jpg b/src/google-meet-icon.jpg similarity index 100% rename from google-meet-icon.jpg rename to src/google-meet-icon.jpg diff --git a/src/google_meet_bot.py b/src/google_meet_bot.py new file mode 100644 index 0000000..e7501e2 --- /dev/null +++ b/src/google_meet_bot.py @@ -0,0 +1,66 @@ +# 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 +from controls import MeetControls +from subtitle_saver_fa import SubtitleSaverFa +from subtitle_saver_fr import SubtitleSaverFr +from subtitle_saver_ch import SubtitleSaverCh + +class GoogleMeetBot: + def __init__(self, email, password, api_key, ai_api_key, update_signal, selected_language): + 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 the subtitle saver based on the selected language + if selected_language == "Farsi": + self.subtitle_saver = SubtitleSaverFa(self.driver, self.translator, self.question_checker, update_signal) + elif selected_language == "French": + self.subtitle_saver = SubtitleSaverFr(self.driver, self.translator, self.question_checker, update_signal) + elif selected_language == "Chinese": + self.subtitle_saver = SubtitleSaverCh(self.driver, self.translator, self.question_checker, update_signal) + + def login(self): + """Log in to Google Meet using the Authenticator class.""" + self.authenticator.login() + + def turn_off_mic_cam(self): + """Turn off microphone and camera using the MeetControls class.""" + self.controls.turn_off_mic_cam() + + def start(self, meeting_link): + """Start the Google Meet session.""" + self.login() + self.driver.get(meeting_link) + self.turn_off_mic_cam() + + # Start the subtitle saver in a separate thread + subtitle_thread = threading.Thread(target=self.subtitle_saver.save_subtitles, daemon=True) + subtitle_thread.start() + + try: + self._keep_running() + except KeyboardInterrupt: + print("Program terminated by user.") + finally: + self.driver.quit() + + def _keep_running(self): + """Keep the bot running until interrupted.""" + while True: + time.sleep(1) diff --git a/src/main.py b/src/main.py new file mode 100644 index 0000000..4b498cd --- /dev/null +++ b/src/main.py @@ -0,0 +1,141 @@ + + +import sys +from PyQt6.QtWidgets import (QApplication, QWidget, QVBoxLayout, + QLineEdit, QPushButton, QTextEdit, QFileDialog, QComboBox) +from PyQt6.QtCore import QThread, pyqtSignal +import driver_setup +from google_meet_bot import GoogleMeetBot +from subtitle_saver_fr import SubtitleSaverFr +from subtitle_saver_fa import SubtitleSaverFa +from subtitle_saver_ch import SubtitleSaverCh +class MeetingThread(QThread): + update_output = pyqtSignal(str) + + def __init__(self, email, password, meeting_link, api_key, ai_api_key, translator, question_checker, selected_language): + 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.translator = translator + self.question_checker = question_checker + self.selected_language = selected_language + + 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.selected_language + ) + self.bot.start(self.meeting_link) + + # Subtitle saving based on selected language + + + subtitle_saver = None + if self.selected_language == "Farsi": + subtitle_saver = SubtitleSaverFa(self.driver, self.translator, self.question_checker, self.update_output) + elif self.selected_language == "French": + subtitle_saver = SubtitleSaverFr(self.driver, self.translator, self.question_checker, self.update_output) + elif self.selected_language == "Chinese": + subtitle_saver = SubtitleSaverCh(self.driver, self.translator, self.question_checker, self.update_output) + + if subtitle_saver: + subtitle_saver.save_subtitles() + +class MainWindow(QWidget): + def __init__(self): + super().__init__() + self.setWindowTitle("Google Meet Bot") + self.setGeometry(400, 400, 800, 800) + + # 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) + 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) + 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) + layout.addWidget(self.meeting_link_input) + + # Create a QComboBox for selecting subtitle language + self.language_combo = QComboBox(self) + self.language_combo.addItems(["Select Language", "Farsi","French", "Chinese"]) + layout.addWidget(self.language_combo) + + self.start_button = QPushButton("Start Meeting", self) + self.start_button.setStyleSheet("background-color: blue; font-size: 16px;") + self.start_button.setFixedHeight(40) + 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) + layout.addWidget(self.output_area) + + self.download_button = QPushButton("Download Text File", self) + self.download_button.setStyleSheet("background-color: green; font-size: 16px;") + self.download_button.setFixedHeight(40) + 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() + selected_language = self.language_combo.currentText() + + if email and password and meeting_link and selected_language != "Select Language": + self.meeting_thread = MeetingThread( + email, + password, + meeting_link, + 'YOUR_API_KEY', + 'YOUR_AI_API_KEY', + self.translator, + self.question_checker, + selected_language # Send the selected language here + ) + self.meeting_thread.update_output.connect(self.append_output) + self.meeting_thread.start() + else: + self.output_area.append("Please fill in all fields and select a language.") + + + 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()) \ No newline at end of file diff --git a/question_checker.py b/src/question_checker.py similarity index 100% rename from question_checker.py rename to src/question_checker.py diff --git a/subtitle_saver_fa.py b/src/subtitle_saver.py similarity index 89% rename from subtitle_saver_fa.py rename to src/subtitle_saver.py index 93743ad..9f83e8f 100644 --- a/subtitle_saver_fa.py +++ b/src/subtitle_saver.py @@ -32,11 +32,9 @@ def save_subtitles(self): translated_text = self.translator.translate(subtitle_text, dest='fa') f.write("Persian: " + translated_text + '\n' + "*" * 20 + '\n') - translated_text2 = self.translator.translate(subtitle_text, dest='fr') - f.write("French: " + translated_text2 + '\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}\n\n French: {translated_text2}") + self.update_signal.emit(f"English: {subtitle_text}\nType: {'Question' if self.question_checker.is_question(subtitle_text) else 'Statement'}\nPersian: {translated_text}\n\n French: {translated_text}") self.previous_subtitles.add(subtitle_text) time.sleep(1) @@ -44,4 +42,4 @@ def save_subtitles(self): print("TimeoutException: Element not found.") except Exception as e: print(f"Error: {e}") - break + break \ No newline at end of file diff --git a/src/subtitle_saver_ch.py b/src/subtitle_saver_ch.py new file mode 100644 index 0000000..47e58c5 --- /dev/null +++ b/src/subtitle_saver_ch.py @@ -0,0 +1,54 @@ +# 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 +from translator import TextTranslator + +class SubtitleSaverCh: + 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: + subtitle_text = self._get_subtitle_text() + if self._is_new_subtitle(subtitle_text): + self._write_subtitle_to_file(f, subtitle_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 + + def _get_subtitle_text(self): + """Retrieve subtitle text from the web element.""" + subtitles = WebDriverWait(self.driver, 10).until( + EC.presence_of_element_located((By.XPATH, '//*[contains(concat(" ", @class, " "), concat(" ", "iOzk7", " "))]'))) + + return subtitles.text.strip() + + def _is_new_subtitle(self, subtitle_text): + """Check if the subtitle is new and not previously recorded.""" + return subtitle_text and subtitle_text not in self.previous_subtitles + + def _write_subtitle_to_file(self, f, subtitle_text): + """Write the subtitle and its translation to the file.""" + f.write("English: " + subtitle_text + '\n' + "*" * 20 + '\n') + + subtitle_type = "Question" if self.question_checker.is_question(subtitle_text) else "Statement" + f.write(f"Type: {subtitle_type}\n" + "*" * 20 + '\n') + + translated_text_ch = self.translator.translate(subtitle_text, dest='zh-TW') + f.write("Chinese: " + translated_text_ch + '\n' + "*" * 20 + '\n') + f.flush() + + self.update_signal.emit(f"English: {subtitle_text}\nType: {subtitle_type}\nChinese: {translated_text_ch}\n\n") diff --git a/src/subtitle_saver_fa.py b/src/subtitle_saver_fa.py new file mode 100644 index 0000000..a4a95af --- /dev/null +++ b/src/subtitle_saver_fa.py @@ -0,0 +1,54 @@ +# 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 +from translator import TextTranslator + +class SubtitleSaverFa: + 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: + subtitle_text = self._get_subtitle_text() + if self._is_new_subtitle(subtitle_text): + self._write_subtitle_to_file(f, subtitle_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 + + def _get_subtitle_text(self): + """Retrieve subtitle text from the web element.""" + subtitles = WebDriverWait(self.driver, 10).until( + EC.presence_of_element_located((By.XPATH, '//*[contains(concat(" ", @class, " "), concat(" ", "iOzk7", " "))]'))) + return subtitles.text.strip() + + def _is_new_subtitle(self, subtitle_text): + """Check if the subtitle is new and not previously recorded.""" + return subtitle_text and subtitle_text not in self.previous_subtitles + + def _write_subtitle_to_file(self, f, subtitle_text): + """Write the subtitle and its translation to the file.""" + f.write("English: " + subtitle_text + '\n' + "*" * 20 + '\n') + + subtitle_type = "Question" if self.question_checker.is_question(subtitle_text) else "Statement" + f.write(f"Type: {subtitle_type}\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: {subtitle_type}\nPersian: {translated_text}\n\n") + diff --git a/src/subtitle_saver_fr.py b/src/subtitle_saver_fr.py new file mode 100644 index 0000000..5c612ed --- /dev/null +++ b/src/subtitle_saver_fr.py @@ -0,0 +1,55 @@ +# 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 +from translator import TextTranslator + + +class SubtitleSaverFr: + 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: + subtitle_text = self._get_subtitle_text() + if self._is_new_subtitle(subtitle_text): + self._write_subtitle_to_file(f, subtitle_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 + + def _get_subtitle_text(self): + """Retrieve subtitle text from the web element.""" + subtitles = WebDriverWait(self.driver, 10).until( + EC.presence_of_element_located((By.XPATH, '//*[contains(concat(" ", @class, " "), concat(" ", "iOzk7", " "))]'))) + return subtitles.text.strip() + + def _is_new_subtitle(self, subtitle_text): + """Check if the subtitle is new and not previously recorded.""" + return subtitle_text and subtitle_text not in self.previous_subtitles + + def _write_subtitle_to_file(self, f, subtitle_text): + """Write the subtitle and its translation to the file.""" + f.write("English: " + subtitle_text + '\n' + "*" * 20 + '\n') + + subtitle_type = "Question" if self.question_checker.is_question(subtitle_text) else "Statement" + f.write(f"Type: {subtitle_type}\n" + "*" * 20 + '\n') + + translated_text = self.translator.translate(subtitle_text, dest='fr') + f.write("French: " + translated_text + '\n' + "*" * 20 + '\n') + f.flush() + + self.update_signal.emit(f"English: {subtitle_text}\nType: {subtitle_type}\nFrench: {translated_text}\n") + diff --git a/src/subtitles.txt b/src/subtitles.txt new file mode 100644 index 0000000..030cb22 --- /dev/null +++ b/src/subtitles.txt @@ -0,0 +1,32 @@ +English: You +Hello. +******************** +Type: Statement +******************** +Chinese: 你 +你好。 +******************** +English: siyamak abasnezhad +Hello. Or are you? +You +What are you? Good. +******************** +Type: Question +******************** +Chinese: Siyamak Abasnezhad +你好。還是你? +你 +你是做什麼的?好的。 +******************** +English: You +What are you? Good luck. +siyamak abasnezhad +Good luck. +******************** +Type: Question +******************** +Chinese: 你 +你是做什麼的?祝你好運。 +Siyamak Abasnezhad +祝你好運。 +******************** diff --git a/src/test.py b/src/test.py new file mode 100644 index 0000000..cf67f72 --- /dev/null +++ b/src/test.py @@ -0,0 +1,171 @@ +# 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() + + + +# 2222222222222222222222222222222222222222222222222222222222222222222222 + + + +import unittest +from unittest.mock import patch, MagicMock +from google_meet_bot import GoogleMeetBot +# from translator import TextTranslator +# from TextTranslator import translate_to_french + +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() + + def test_subtitle_saving(self): + # Test subtitle saving functionality based on language + self.bot.selected_language = "Farsi" + self.bot.subtitle_saver.save_subtitles = MagicMock() + self.bot.start("https://meet.google.com/test-meeting") + self.bot.subtitle_saver.save_subtitles.assert_called_once() + self.bot.translator.translate_to_french.assert_called_once() + + def test_translator_called(self): + # Test that the translator is called during the meeting + self.bot.start("https://meet.google.com/test-meeting") + self.bot.translator.translate.assert_called() # Assuming translate method exists + + def test_question_checker_called(self): + # Test that the question checker is called during the meeting + self.bot.start("https://meet.google.com/test-meeting") + self.bot.question_checker.check_question.assert_called() # Assuming check_question method exists + + @patch('google_meet_bot.time.sleep', return_value=None) # Mock sleep to avoid delays + def test_login_failure(self, mock_sleep): + # Simulate a login failure + self.bot.authenticator.login.side_effect = Exception("Login failed") + with self.assertRaises(Exception) as context: + self.bot.start("https://meet.google.com/test-meeting") + self.assertEqual(str(context.exception), "Login failed") + +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..2a00fc3 --- /dev/null +++ b/src/translator.py @@ -0,0 +1,21 @@ +# translator.py +from googletrans import Translator + +class TextTranslator: + def __init__(self): + self.translator = Translator() + + def translate(self, text, dest='fa'): + """Translate text to the specified destination language.""" + return self.translator.translate(text, dest=dest).text + + def translate_to_french(self, text): + """Translate text to French.""" + return self.translate(text, dest='fr') + + def translate_to_chinese(self, text): + """Translate text to Chinese (Traditional).""" + return self.translate(text, dest='zh-TW') + def is_question(self, text): + # Simple check to see if the text is a question + return text.strip().endswith('?') \ No newline at end of file diff --git a/subtitle_saver_ch.py b/subtitle_saver_ch.py deleted file mode 100644 index bc1b17e..0000000 --- a/subtitle_saver_ch.py +++ /dev/null @@ -1,46 +0,0 @@ -# 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_ch: - 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_ch(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_ch = self.translator.translate(subtitle_text, dest='zh-TW') - f.write("Persian: " + translated_text_ch + '\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'}\nChinese: {translated_text_ch}\n\n French: {translated_text2}") - - 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/subtitle_saver_fr.py b/subtitle_saver_fr.py deleted file mode 100644 index 644e865..0000000 --- a/subtitle_saver_fr.py +++ /dev/null @@ -1,44 +0,0 @@ -# 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_fr: - 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_fr(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_text2 = self.translator.translate(subtitle_text, dest='fr') - f.write("French: " + translated_text2 + '\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'}\nFrench: {translated_text2}") - - 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/subtitles.txt b/subtitles.txt index c764097..9dc1a39 100644 --- a/subtitles.txt +++ b/subtitles.txt @@ -1,17 +1,32 @@ -English: You -Test the testing. +English: siyamak abasnezhad +Hello. ******************** Type: Statement ******************** -English: You -Hello there. -******************** -Type: Statement +Persian: سیهاماک آبرس +سلام ******************** -English: Hello -Hello. +English: siyamak abasnezhad +Iron Man. You -Hello. +I ******************** Type: Statement ******************** +Persian: سیهاماک آبرس +مرد آهنین +شما +من +******************** +English: You +I What? +siyamak abasnezhad +so, you doing +******************** +Type: Question +******************** +Persian: شما +من چی؟ +سیهاماک آبرس +بنابراین ، شما انجام می دهید +******************** diff --git a/test.py b/test.py deleted file mode 100644 index f81ac8a..0000000 --- a/test.py +++ /dev/null @@ -1,68 +0,0 @@ -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/translator.py b/translator.py deleted file mode 100644 index 3ed0bfc..0000000 --- a/translator.py +++ /dev/null @@ -1,16 +0,0 @@ -# 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 - - # def translate2(self, text, dest='fr'): - # return self.translator.translate(text, dest=dest).text - - # def translate3(self, text, dest='it'): - # return self.translator.translate(text, dest=dest).text -