├── .gitignore ├── LICENSE ├── README.md ├── defaults └── startup_cookies.pkl ├── requirements.txt ├── sample_usage.py ├── utils ├── delay_config.py ├── exceptions.py ├── pprints.py ├── services.py └── solver.py └── yahoo_automator.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # PyCharm 156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 158 | # and can be added to the global gitignore or merged into this file. For a more nuclear 159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 160 | #.idea/ 161 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Abdul Moez 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | YahooAutomator: Automated Yahoo Login Tool 2 | ==== 3 | YahooAutomator, an automated tool designed for simplifying the Yahoo login process. 4 | This tool is specifically created for educational purposes to help individuals understand 5 | web automation and the challenges involved in automating login processes. 6 | 7 | Important Notice 8 | ---- 9 | * **Educational Purpose Only**: YahooAutomator is intended for educational purposes, helping users learn about 10 | web automation and login processes. 11 | * **Responsible Usage**: Do not use this tool for malicious activities or unauthorized access. 12 | Respect the privacy and rights of others. 13 | * **Security**: If you discover any security vulnerabilities or issues related to this tool, please responsibly 14 | disclose them to the author. 15 | 16 | Author Information 17 | ---- 18 | * **Author**: Abdul Moez 19 | * **Version**: 0.1 20 | * **Study**: Undergraduate at GCU Lahore, Pakistan 21 | 22 | Remember, unauthorized access to accounts or systems is illegal and unethical. 23 | Use YahooAutomator responsibly, following cybersecurity and privacy principles. 24 | 25 | ----------- 26 | Video Demo 27 | ----------- 28 | [![Video Demo](https://img.youtube.com/vi/x1DDWoVKnK0/0.jpg)](https://www.youtube.com/watch?v=x1DDWoVKnK0) 29 | ----------- 30 | 31 | 32 | Introduction 33 | ----- 34 | YahooAutomator provides functionality to automate the login to a 35 | Yahoo account using provided credentials, handle various challenges 36 | such as CAPTCHA, and interact with the Yahoo login page seamlessly. 37 | This tool aims to facilitate learning about web automation while emphasizing 38 | responsible and ethical usage. 39 | 40 | Features 41 | ----- 42 | * **Automated Yahoo Login**: Effortlessly log in to your Yahoo account using the provided email and password. 43 | * **Challenges Handling**: Navigate through common challenges like CAPTCHA to ensure a smooth login process. 44 | * **Educational Purpose**: Designed for educational purposes, YahooAutomator serves as a valuable tool to 45 | understand web automation concepts. 46 | 47 | Getting Started 48 | ----- 49 | To use YahooAutomator, follow these simple steps: 50 | 1. **Install FFmpeg**: 51 | * Windows 52 | ```shell 53 | winget install --id=Gyan.FFmpeg -e 54 | ``` 55 | * Linux 56 | ```shell 57 | sudo apt install ffmpeg 58 | ``` 59 | * MacOS 60 | ```shell 61 | brew install ffmpeg 62 | ``` 63 | 2. Install Python Packages: 64 | ```shell 65 | pip3 install -r requirements.txt 66 | ``` 67 | 3. Sample Usage: 68 | ```python 69 | from yahoo_automator import YahooAutomator 70 | 71 | yahoo_automator = YahooAutomator( 72 | profile_dir="./temp_profile", 73 | predefined_cookies="./defaults/startup_cookies.pkl" 74 | ) 75 | 76 | status = yahoo_automator.login_to_yahoo( 77 | yahoo_mail="yahoo@yahoo.com", 78 | passwords_combo=["wrong_pass121", "wrong_pass212", "correctpassword"] 79 | ) 80 | 81 | if status[0]: 82 | print(f"Successfully logged in with password: {yahoo_automator.correct_password}") 83 | else: 84 | print(f"Login failed. Reason: {status[1]}") 85 | ``` 86 | 87 | YahooAutomator Parameters 88 | ---- 89 | - `profile_dir (str)`: Path to the profile directory. 90 | - `predefined_cookies (str)`: Path to the file containing predefined cookies. 91 | - `estimated_wait (int)`: Estimated wait time for various actions. 92 | - `short_wait (int)`: Short wait time for specific actions. 93 | - `verbose (bool)`: Enable verbose mode for printing status messages. 94 | - `headless_mode (bool)`: Run the browser in headless mode. 95 | 96 | YahooAutomator Public Methods 97 | ---- 98 | * `correct_password() -> str`: Returns the correct password used for a successful login. 99 | * `get_browser() -> WebDriver`: Get the WebDriver object for the current browser session. 100 | * `login_to_yahoo(yahoo_mail: str, passwords_combo: list[str], first_run: bool = True, pace: float = 0.5) -> tuple[bool, str]`: Logs in to a Yahoo account using provided credentials. 101 | 102 | 103 | Important tips 104 | ----- 105 | * If you are using this tool to automate the emailing process, you cannot use any browser other than the one 106 | provided by this tool. You can create or utilize browsers created by the `login_to_yahoo` function 107 | automatically through the `get_browser()` function. It creates a detection-free 108 | browser for seamless integration with Yahoo. 109 | * Occasionally, you may face a **one-hour** ban from Yahoo, especially when attempting to log in for the 110 | first time without predefined cookies. In such cases, please wait for one hour before resuming your work. 111 | 112 | # Contributor 113 | 114 | 115 | 116 | 117 | 118 | ----------- 119 | Support and Contact Information 120 | ---------- 121 | > If you require any assistance or have questions, please feel free to reach out to me through the following channels: 122 | * **Email**: `abdulmoez123456789@gmail.com` 123 | 124 | > I have also established a dedicated Discord group for more interactive communication: 125 | * **Discord Server**: `https://discord.gg/RMNcqzmt9f` 126 | 127 | 128 | ----------- 129 | 130 | Buy Me a coffee 131 | -------------- 132 | __If you'd like to show your support and appreciation for my work, you can buy me a coffee using the 133 | following payment option:__ 134 | 135 | **Payoneer**: `abdulmoez123456789@gmail.com` 136 | 137 | > Your support is greatly appreciated and helps me continue providing valuable assistance and resources. 138 | Thank you for your consideration. 139 | 140 | -------------------------------------------------------------------------------- /defaults/startup_cookies.pkl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Anonym0usWork1221/YahooAutomator/da55b5722adfe846f272136cebcebdcfd8df4d28/defaults/startup_cookies.pkl -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | async-generator==1.10 2 | attrs==22.2.0 3 | certifi==2023.7.22 4 | cffi==1.16.0 5 | charset-normalizer==3.0.1 6 | chromedriver-autoinstaller==0.6.3 7 | exceptiongroup==1.1.0 8 | h11==0.14.0 9 | idna==3.4 10 | iniconfig==2.0.0 11 | outcome==1.2.0 12 | packaging==23.2 13 | pluggy==1.0.0 14 | psutil==5.9.6 15 | pycparser==2.21 16 | pydub==0.25.1 17 | PySocks==1.7.1 18 | requests==2.31.0 19 | selenium==4.16.0 20 | sniffio==1.3.0 21 | sortedcontainers==2.4.0 22 | SpeechRecognition==3.8.1 23 | tomli==2.0.1 24 | trio==0.22.0 25 | trio-websocket==0.9.2 26 | undetected-chromedriver==3.5.4 27 | urllib3==1.26.14 28 | websockets==12.0 29 | wsproto==1.2.0 30 | -------------------------------------------------------------------------------- /sample_usage.py: -------------------------------------------------------------------------------- 1 | from yahoo_automator import YahooAutomator 2 | # winget install --id=Gyan.FFmpeg -e 3 | # sudo apt install ffmpeg 4 | 5 | yahoo_automator = YahooAutomator(profile_dir="./temp_profile", 6 | predefined_cookies="./defaults/startup_cookies.pkl" 7 | ) 8 | 9 | status = yahoo_automator.login_to_yahoo( 10 | yahoo_mail="yahoo@yahoo.com", 11 | passwords_combo=["wrong_pass121", "wrong_pass212", "correctpassword"]) 12 | 13 | if status[0]: 14 | print(f"Found password: {yahoo_automator.correct_password}") 15 | else: 16 | print(status) 17 | print(yahoo_automator.correct_password) 18 | -------------------------------------------------------------------------------- /utils/delay_config.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | import random 3 | import time 4 | 5 | 6 | class DelayConfig(ABC): 7 | """Abstract base class for delay configurations for :class:`RecaptchaSolver`.""" 8 | 9 | @abstractmethod 10 | def delay_after_click_checkbox(self): 11 | pass 12 | 13 | @abstractmethod 14 | def delay_after_click_audio_button(self): 15 | pass 16 | 17 | @abstractmethod 18 | def delay_after_click_verify_button(self): 19 | pass 20 | 21 | 22 | class StandardDelayConfig(DelayConfig): 23 | def __init__(self, min_delay: float = 0.75, max_delay: float = 1.25) -> None: 24 | self.min_delay = min_delay 25 | self.max_delay = max_delay 26 | 27 | def _sleep_random(self) -> None: 28 | time.sleep(random.uniform(self.min_delay, self.max_delay)) 29 | 30 | def delay_after_click_checkbox(self): 31 | self._sleep_random() 32 | 33 | def delay_after_click_audio_button(self): 34 | self._sleep_random() 35 | 36 | def delay_after_click_verify_button(self): 37 | self._sleep_random() 38 | -------------------------------------------------------------------------------- /utils/exceptions.py: -------------------------------------------------------------------------------- 1 | class RecaptchaException(Exception): 2 | pass 3 | -------------------------------------------------------------------------------- /utils/pprints.py: -------------------------------------------------------------------------------- 1 | """ 2 | PPrints Module 3 | This module defines the PPrints class, which provides utilities for printing formatted information and handling logs. 4 | Classes: 5 | PPrints: A utility class for pretty printing information and handling logs. 6 | """ 7 | 8 | # Import necessary modules 9 | 10 | from platform import system as system_platform 11 | from threading import active_count 12 | from os.path import isfile 13 | from psutil import Process 14 | from os import system 15 | 16 | 17 | class PPrints: 18 | """ 19 | PPrints Class 20 | This class encapsulates methods for printing formatted information and optionally logging it. 21 | Attributes: 22 | _HEADER (str): ANSI escape sequence for header color. 23 | _BLUE (str): ANSI escape sequence for blue color. 24 | _CYAN (str): ANSI escape sequence for cyan color. 25 | _GREEN (str): ANSI escape sequence for green color. 26 | _WARNING (str): ANSI escape sequence for warning color. 27 | _RED (str): ANSI escape sequence for red color. 28 | _RESET (str): ANSI escape sequence to reset text color. 29 | Methods: 30 | __init__(self, logs_file="logs.txt"): Initialize the PPrints object. 31 | clean_terminal(): Clean the terminal screen based on the platform. 32 | pretty_print(current_site, status, mode, limit, total_results="Calculating", 33 | current_index="Calculating", logs=False): Print formatted information with optional logging. 34 | """ 35 | 36 | # Private elements 37 | _HEADER = '\033[95m' 38 | _BLUE = '\033[94m' 39 | _CYAN = '\033[96m' 40 | _GREEN = '\033[92m' 41 | _WARNING = '\033[93m' 42 | _RED = '\033[91m' 43 | _RESET = '\033[0m' 44 | 45 | def __init__(self, logs_file: str = "logs.txt") -> None: 46 | """ 47 | Initialize the PPrints object. 48 | Args: 49 | logs_file (str): The name of the log file to write logs to. 50 | """ 51 | 52 | self._process = Process() 53 | self._log_file = logs_file 54 | 55 | @staticmethod 56 | def clean_terminal() -> str: 57 | """ 58 | Clean the terminal screen based on the platform. 59 | Returns: 60 | str: The name of the platform. 61 | """ 62 | 63 | if system_platform().lower() == "windows": 64 | system("cls") 65 | else: 66 | system("clear") 67 | return system_platform() 68 | 69 | def pretty_print(self, 70 | status: str, 71 | mode: str, 72 | logs: bool = False, 73 | ) -> None: 74 | """ 75 | Print formatted information along with optional logging. 76 | Args: 77 | status (str): The status of the process. 78 | mode (str): The mode of operation. 79 | logs (bool, optional): Whether to log the information. Defaults to False. 80 | """ 81 | 82 | memory_info = self._process.memory_info() 83 | current_memory_usage = memory_info.rss / 1024 / 1024 # Convert bytes to megabytes 84 | 85 | print(f"{self._GREEN}Platform: {self.clean_terminal()}\n" 86 | f"{self._WARNING}Status: {status}\n" 87 | f"{self._BLUE}Mode: {mode}\n" 88 | f"{self._BLUE}LaunchedDrivers: {active_count()}\n" 89 | f"{self._RED}MemoryUsageByScript: {current_memory_usage: .2f}MB\n{self._RESET}") 90 | if logs: 91 | log_msg = f"Status: {status}\n" \ 92 | f"Mode: {mode}\n\n" 93 | if isfile(self._log_file): 94 | with open(self._log_file, 'a') as file_obj: 95 | file_obj.write(log_msg) 96 | else: 97 | with open(self._log_file, 'w') as file_obj: 98 | file_obj.write(log_msg) 99 | -------------------------------------------------------------------------------- /utils/services.py: -------------------------------------------------------------------------------- 1 | from speech_recognition import AudioData 2 | from abc import ABC, abstractmethod 3 | from typing import Any, Optional 4 | import speech_recognition as sr 5 | 6 | 7 | class Service(ABC): 8 | """Abstract base class for speech recognition services.""" 9 | 10 | @abstractmethod 11 | def __init__(self) -> None: 12 | pass 13 | 14 | @abstractmethod 15 | def recognize(self, recognizer: sr.Recognizer, audio_data: AudioData, language: str = 'en-US') -> Any: 16 | """Perform speech recognition on the given audio data using the given recognizer.""" 17 | pass 18 | 19 | 20 | class GoogleService(Service): 21 | """ 22 | Service for Google Speech Recognition API. 23 | 24 | See docs for `speech_recognition.Recognizer.recognize_google` for details on congfiguration. 25 | """ 26 | 27 | def __init__( 28 | self, 29 | key: Optional[Any] = None, 30 | ) -> None: 31 | self.key = key 32 | 33 | def recognize(self, recognizer: sr.Recognizer, audio_data: AudioData, language: str = 'en-US') -> Any: 34 | return recognizer.recognize_google(audio_data, key=self.key, language=language) 35 | 36 | 37 | class GoogleCloudService(Service): 38 | """ 39 | Service for Google Cloud Speech API. 40 | 41 | See docs for `speech_recognition.Recognizer.recognize_google_cloud` for details on congfiguration. 42 | """ 43 | 44 | def __init__( 45 | self, 46 | credentials_json: Optional[Any] = None, 47 | ) -> None: 48 | self.credentials_json = credentials_json 49 | 50 | def recognize(self, recognizer: sr.Recognizer, audio_data: AudioData, language: str = 'en-US') -> Any: 51 | return recognizer.recognize_google_cloud( 52 | audio_data, credentials_json=self.credentials_json 53 | ) 54 | -------------------------------------------------------------------------------- /utils/solver.py: -------------------------------------------------------------------------------- 1 | from selenium.webdriver.support import expected_conditions as ec 2 | from selenium.webdriver.remote.webelement import WebElement 3 | from selenium.webdriver.chrome.webdriver import WebDriver 4 | from selenium.webdriver.support.ui import WebDriverWait 5 | from selenium.common.exceptions import TimeoutException 6 | from utils.services import Service, GoogleService 7 | from utils.exceptions import RecaptchaException 8 | from selenium.webdriver.common.by import By 9 | from utils.delay_config import DelayConfig 10 | import speech_recognition as sr 11 | from pydub import AudioSegment 12 | from typing import Optional 13 | import tempfile 14 | import requests 15 | import random 16 | import uuid 17 | import time 18 | import os 19 | 20 | DEFAULT_SERVICE: Service = GoogleService() 21 | 22 | 23 | class RecaptchaSolver: 24 | def __init__( 25 | self, 26 | driver: WebDriver, 27 | service: Service = DEFAULT_SERVICE, 28 | service_language: str = 'en-US', 29 | delay_config: Optional[DelayConfig] = None, 30 | ): 31 | """ 32 | :param driver: Selenium web driver to use to solve the captcha 33 | :param service: service to use for speech recognition (defaults to ``GoogleService``). 34 | See the ``services`` module for available services. 35 | :param service_language: Language to use when recognizing speech to solve reCAPTCHA challenge (en-US by default for American English recognition) 36 | :param delay_config: if set, use the given configuration for delays between UI interactions. 37 | See :class:`DelayConfig`, and also :class:`StandardDelayConfig`, which provides a standard implementation that should work in many cases. 38 | """ 39 | 40 | self._driver = driver 41 | self._service = service 42 | self._delay_config = delay_config 43 | self._language = service_language 44 | 45 | # Initialise speech recognition API object 46 | self._recognizer = sr.Recognizer() 47 | 48 | def click_recaptcha_v2(self, iframe: WebElement, by_selector: Optional[str] = None) -> None: 49 | """ 50 | Click the "I'm not a robot" checkbox and then solve a reCAPTCHA v2 challenge. 51 | 52 | Call this method directly on web pages with an "I'm not a robot" checkbox. See for details of how this works. 53 | 54 | :param iframe: web element for inline frame of reCAPTCHA to solve 55 | :param by_selector: By selector to use to find the iframe, if ``iframe`` is a string 56 | :raises selenium.common.exceptions.TimeoutException: if a timeout occurred while waiting 57 | """ 58 | 59 | if isinstance(iframe, str): 60 | WebDriverWait(self._driver, 10).until( 61 | ec.frame_to_be_available_and_switch_to_it((by_selector, iframe))) 62 | 63 | else: 64 | self._driver.switch_to.frame(iframe) 65 | 66 | checkbox = self._wait_for_element( 67 | by='id', 68 | locator='recaptcha-anchor', 69 | timeout=10, 70 | ) 71 | 72 | self._js_click(checkbox) 73 | 74 | if checkbox.get_attribute('aria-checked') == 'true': 75 | return 76 | 77 | if self._delay_config: 78 | self._delay_config.delay_after_click_checkbox() 79 | 80 | self._driver.switch_to.parent_frame() 81 | 82 | captcha_challenge = self._wait_for_element( 83 | by=By.XPATH, 84 | locator='//iframe[contains(@src, "recaptcha") and contains(@src, "bframe")]', 85 | timeout=5, 86 | ) 87 | 88 | self.solve_recaptcha_v2_challenge(iframe=captcha_challenge) 89 | 90 | def solve_recaptcha_v2_challenge(self, iframe: WebElement) -> None: 91 | """ 92 | Solve a reCAPTCHA v2 challenge that has already appeared. 93 | 94 | Call this method directly on web pages with the "invisible reCAPTCHA" badge. See for details of how this works. 95 | 96 | :param iframe: Web element for inline frame of reCAPTCHA to solve 97 | :raises selenium.common.exceptions.TimeoutException: if a timeout occurred while waiting 98 | """ 99 | 100 | self._driver.switch_to.frame(iframe) 101 | 102 | # If the captcha image audio is available, locate it. Otherwise, skip to the next line of code. 103 | 104 | try: 105 | self._wait_for_element( 106 | by=By.XPATH, 107 | locator='//*[@id="recaptcha-audio-button"]', 108 | timeout=1, 109 | ).click() 110 | 111 | except TimeoutException: 112 | pass 113 | 114 | self._solve_audio_challenge(self._language) 115 | 116 | # Locate verify button and click it via JavaScript 117 | verify_button = self._wait_for_element( 118 | by=By.ID, 119 | locator='recaptcha-verify-button', 120 | timeout=5, 121 | ) 122 | 123 | self._js_click(verify_button) 124 | 125 | if self._delay_config: 126 | self._delay_config.delay_after_click_verify_button() 127 | 128 | try: 129 | self._wait_for_element( 130 | by=By.XPATH, 131 | locator='//div[normalize-space()="Multiple correct solutions required - please solve more."]', 132 | timeout=1, 133 | ) 134 | 135 | self._solve_audio_challenge(self._language) 136 | 137 | # Locate verify button again to avoid stale element reference and click it via JavaScript 138 | second_verify_button = self._wait_for_element( 139 | by=By.ID, 140 | locator='recaptcha-verify-button', 141 | timeout=5, 142 | ) 143 | 144 | self._js_click(second_verify_button) 145 | 146 | except TimeoutException: 147 | pass 148 | 149 | self._driver.switch_to.parent_frame() 150 | 151 | def _solve_audio_challenge(self, language: str) -> None: 152 | try: 153 | # Locate audio challenge download link 154 | download_link: WebElement = self._wait_for_element( 155 | by=By.CLASS_NAME, 156 | locator='rc-audiochallenge-tdownload-link', 157 | timeout=10, 158 | ) 159 | 160 | except TimeoutException: 161 | raise RecaptchaException('Google has detected automated queries. Try again later.') 162 | 163 | # Create temporary directory and temporary files 164 | tmp_dir = tempfile.gettempdir() 165 | 166 | id_ = uuid.uuid4().hex 167 | 168 | mp3_file, wav_file = os.path.join(tmp_dir, f'{id_}_tmp.mp3'), os.path.join(tmp_dir, f'{id_}_tmp.wav') 169 | 170 | tmp_files = {mp3_file, wav_file} 171 | 172 | with open(mp3_file, 'wb') as f: 173 | link = download_link.get_attribute('href') 174 | 175 | audio_download = requests.get(url=link, allow_redirects=True) 176 | 177 | f.write(audio_download.content) 178 | 179 | f.close() 180 | 181 | # Convert MP3 to WAV format for compatibility with speech recognizer APIs 182 | AudioSegment.from_mp3(mp3_file).export(wav_file, format='wav') 183 | 184 | # Disable dynamic energy threshold to avoid failed reCAPTCHA audio transcription due to static noise 185 | self._recognizer.dynamic_energy_threshold = False 186 | 187 | with sr.AudioFile(wav_file) as source: 188 | audio = self._recognizer.listen(source) 189 | 190 | try: 191 | recognized_text = self._service.recognize(self._recognizer, audio, language) 192 | 193 | except sr.UnknownValueError: 194 | raise RecaptchaException('Speech recognition API could not understand audio, try again') 195 | 196 | # Clean up all temporary files 197 | for path in tmp_files: 198 | if os.path.exists(path): 199 | os.remove(path) 200 | 201 | # Write transcribed text to iframe's input box 202 | response_textbox = self._driver.find_element(By.ID, 'audio-response') 203 | 204 | self._human_type(element=response_textbox, text=recognized_text) 205 | 206 | def _js_click(self, element: WebElement) -> None: 207 | """ 208 | Perform click on given web element using JavaScript. 209 | 210 | :param element: web element to click 211 | """ 212 | 213 | self._driver.execute_script('arguments[0].click();', element) 214 | 215 | def _wait_for_element( 216 | self, 217 | by: str = By.ID, 218 | locator: Optional[str] = None, 219 | timeout: float = 10, 220 | ) -> WebElement: 221 | """ 222 | Try to locate web element within given duration. 223 | 224 | :param by: strategy to use to locate element (see class `selenium.webdriver.common.by.By`) 225 | :param locator: locator that identifies the element 226 | :param timeout: number of seconds to wait for element before raising `TimeoutError` 227 | :return: located web element 228 | :raises selenium.common.exceptions.TimeoutException: if element is not located within given duration 229 | """ 230 | 231 | return WebDriverWait(self._driver, timeout).until(ec.visibility_of_element_located((by, locator))) 232 | 233 | @staticmethod 234 | def _human_type(element: WebElement, text: str) -> None: 235 | """ 236 | Types in a way reminiscent of a human, with a random delay in between 50ms to 100ms for every character 237 | :param element: Input element to type text to 238 | :param text: Input to be typed 239 | """ 240 | 241 | for c in text: 242 | element.send_keys(c) 243 | 244 | time.sleep(random.uniform(0.05, 0.1)) 245 | 246 | 247 | # Add alias for backwards compatibility 248 | API = RecaptchaSolver 249 | -------------------------------------------------------------------------------- /yahoo_automator.py: -------------------------------------------------------------------------------- 1 | """ 2 | ******************************************************************************* 3 | WARNING: USE THIS TOOL RESPONSIBLY 4 | ******************************************************************************* 5 | 6 | This YahooAutomator tool, developed by Abdul Moez, is intended for educational purposes only and should be used 7 | responsibly and ethically. The tool automates the Yahoo login process, providing functionality to log in to a 8 | Yahoo account using provided credentials, handle various challenges like CAPTCHA, 9 | and interact with the Yahoo login page. 10 | 11 | By using this tool, you acknowledge and agree to the following: 12 | 13 | 1. Educational Purpose Only: 14 | This tool is meant for educational purposes, helping individuals understand web automation and 15 | the challenges involved in automating login processes. 16 | 17 | 2. Not for Malicious Activities: 18 | Do not use this tool for any malicious or harmful activities, including but not limited to unauthorized 19 | access, data theft, or any actions that violate the law or Yahoo's terms of service. 20 | 21 | 3. Responsibility: 22 | The author, Abdul Moez, is not responsible for any misuse, damage, or legal consequences resulting from 23 | the use of this tool. Users are solely responsible for their actions. 24 | 25 | 4. User Accountability: 26 | Users of this tool should exercise caution, adhere to legal and ethical standards, and respect the 27 | privacy and rights of others. Any consequences arising from misuse will be the user's responsibility. 28 | 29 | 5. No Warranty: 30 | This tool comes with no warranty. The author provides no guarantees regarding its performance, 31 | accuracy, or suitability for any specific purpose. 32 | 33 | 6. Report Security Issues: 34 | If you discover any security vulnerabilities or issues related to this tool, 35 | please responsibly disclose them to the author. 36 | 37 | 7. Author information: 38 | - author: Abdul Moez 39 | - version: 0.1 40 | - study: Undergraduate at GCU Lahore, Pakistan 41 | 42 | Remember that unauthorized access to accounts or systems is illegal and unethical. Use this tool responsibly, 43 | respecting the principles of cybersecurity and privacy. 44 | 45 | ******************************************************************************* 46 | """ 47 | 48 | from selenium.common.exceptions import NoSuchElementException, TimeoutException 49 | from selenium.webdriver.common.action_chains import ActionChains 50 | from undetected_chromedriver import Chrome, ChromeOptions 51 | from selenium.webdriver.chrome.webdriver import WebDriver 52 | from utils.exceptions import RecaptchaException 53 | from selenium.webdriver.common.by import By 54 | from utils.solver import RecaptchaSolver 55 | from utils.pprints import PPrints 56 | import chromedriver_autoinstaller 57 | from os import path, makedirs 58 | from pickle import load 59 | from time import sleep 60 | from re import search 61 | 62 | 63 | class YahooAutomator(object): 64 | """ 65 | YahooAutomator is a class designed for automating the Yahoo login process. It provides functionality to log in 66 | to a Yahoo account using provided credentials, handle various challenges like CAPTCHA, and interact with the 67 | Yahoo login page. 68 | 69 | Attributes: 70 | __TARGET_SITE (str): The target URL for Yahoo login. 71 | __class_new_account (str): Selector for the 'Create New Account' link. 72 | __class_password_error (str): Selector for the element displaying password-related errors. 73 | __class_unexpected_error (str): Selector for the element displaying unexpected errors. 74 | __id_username (str): Selector for the username input field. 75 | __id_password (str): Selector for the password input field. 76 | __id_banned_time (str): Selector for the element displaying the time remaining for a first-time ban. 77 | __id_captcha_submit_btn (str): Selector for the reCAPTCHA submit button. 78 | __id_password_signin (str): Selector for the 'Sign In' button after entering the password. 79 | __name_email_signin (str): Selector for the 'Sign In' button after entering the email. 80 | __css_recaptcha_iframe_1 (str): CSS selector for the first reCAPTCHA iframe. 81 | __xpath_recaptcha_iframe_2 (str): XPath selector for the second reCAPTCHA iframe. 82 | __banned_time_exp (str): Regular expression for extracting remaining time from a ban message. 83 | 84 | Methods: 85 | __init__(self, profile_dir: str, predefined_cookies: str, estimated_wait: int = 10, short_wait: int = 5, 86 | verbose: bool = True, headless_mode: bool = False) -> None: 87 | Initializes the YahooAutomator object. 88 | 89 | correct_password(self) -> str: 90 | Returns the correct password used for a successful login. 91 | 92 | __pprints_override(self, status: str, logs: bool = False) -> None: 93 | Overrides and prints status messages using the PPrints class. 94 | 95 | __created_patched_driver(self) -> WebDriver: 96 | Creates and returns a patched WebDriver for interacting with the browser. 97 | 98 | get_browser(self) -> WebDriver: 99 | Get the WebDriver object for the current browser session. 100 | 101 | __validate_directories(self) -> None: 102 | Validates the existence of the profile directory and creates it if necessary. 103 | 104 | __skip_old_login_page(self) -> None: 105 | Skip the old Yahoo login page if present. 106 | 107 | __insert_email(self, email: str, pace: float = .5) -> bool: 108 | Inserts the Yahoo email address into the login form. 109 | 110 | __check_yahoo_first_time_ban(self) -> tuple[bool, str]: 111 | Checks if the Yahoo account is first-time banned. 112 | 113 | __solve_captcha(self) -> tuple[bool, str]: 114 | Solves the reCAPTCHA challenge if present. 115 | 116 | __insert_password(self, password: str, pace: float = 0.5) -> bool: 117 | Inserts the password into the login form. 118 | 119 | __is_password_correct(self) -> bool: 120 | Checks if the last entered password is correct. 121 | 122 | __is_unexpected_error(self) -> tuple[bool, str]: 123 | Checks if there is an unexpected error during the login process. 124 | 125 | login_to_yahoo(self, yahoo_mail: str, passwords_combo: list[str], first_run: bool = True, 126 | pace: float = 0.5) -> tuple[bool, str]: 127 | Logs in to a Yahoo account using provided credentials. 128 | """ 129 | 130 | __TARGET_SITE: str = "https://login.yahoo.com/" 131 | 132 | # selectors 133 | __class_new_account: str = "bottom-cta" 134 | __class_password_error: str = "error-msg" 135 | __class_unexpected_error: str = "notification-error" 136 | __id_username: str = "login-username" 137 | __id_password: str = "login-passwd" 138 | __id_banned_time: str = "wait-challenge" 139 | __id_captcha_submit_btn: str = "recaptcha-submit" 140 | __id_password_signin: str = "login-signin" 141 | __name_email_signin: str = "signin" 142 | __css_recaptcha_iframe_1: str = 'iframe[id="recaptcha-iframe"]' 143 | __xpath_recaptcha_iframe_2: str = '//iframe[@title="reCAPTCHA"]' 144 | 145 | # regular expressions 146 | __banned_time_exp: str = r"Try again in (\d+) minutes" 147 | 148 | def __init__(self, profile_dir: str, predefined_cookies: str, estimated_wait: int = 10, short_wait: int = 5, 149 | verbose: bool = True, headless_mode: bool = False) -> None: 150 | """ 151 | Initialize the YahooAutomator object. 152 | 153 | Parameters: 154 | - profile_dir (str): Path to the profile directory. 155 | - predefined_cookies (str): Path to the file containing predefined cookies. 156 | - estimated_wait (int): Estimated wait time for various actions. 157 | - short_wait (int): Short wait time for specific actions. 158 | - verbose (bool): Enable verbose mode for printing status messages. 159 | - headless_mode (bool): Run the browser in headless mode. 160 | 161 | Returns: 162 | None 163 | """ 164 | 165 | self.__mode: str = "Headless" if headless_mode else "Windowed" 166 | self.__current_driver: any([None, WebDriver]) = None 167 | self.__predefined_cookies: str = predefined_cookies 168 | self.__estimated_wait: int = estimated_wait 169 | self.__headless: bool = headless_mode 170 | self.__profile_dir: str = profile_dir 171 | self.__short_wait: int = short_wait 172 | self.__pprints: PPrints = PPrints() 173 | self.__verbose: bool = verbose 174 | self.__validate_directories() 175 | self.__founded_password: str = "" 176 | 177 | def __validate_directories(self): 178 | """ 179 | Validate the existence of the profile directory and create it if necessary. 180 | Returns: 181 | None 182 | """ 183 | 184 | profile_dir: str = path.abspath(self.__profile_dir) 185 | makedirs(name=profile_dir, exist_ok=True) 186 | 187 | @property 188 | def correct_password(self) -> str: 189 | """ 190 | Get the correct password used for successful login. 191 | 192 | Returns: 193 | str: Correct password. 194 | """ 195 | 196 | return self.__founded_password 197 | 198 | def __pprints_override(self, status: str, logs: bool = False) -> None: 199 | """ 200 | Override and print status messages using PPrints class. 201 | 202 | Parameters: 203 | - status (str): Status message to be printed. 204 | - logs (bool): Flag indicating whether to print logs. 205 | 206 | Returns: 207 | None 208 | """ 209 | 210 | if self.__verbose: 211 | self.__pprints.pretty_print(status=status, mode=self.__mode, logs=logs) 212 | 213 | def __created_patched_driver(self) -> WebDriver: 214 | """ 215 | Create and return a patched WebDriver for interacting with the browser. 216 | Returns: 217 | WebDriver: The patched WebDriver object. 218 | """ 219 | 220 | self.__pprints_override(status="Initializing patched browser") 221 | version_main = int(chromedriver_autoinstaller.get_chrome_version().split(".")[0]) 222 | profile_dir: str = path.abspath(self.__profile_dir) 223 | options = ChromeOptions() 224 | options.add_argument('--allow-running-insecure-content') 225 | options.add_argument(f'--user-data-dir={profile_dir}') 226 | options.add_argument('--ignore-certificate-errors') 227 | options.add_argument("--disable-notifications") 228 | options.add_argument("--window-size=1920,1080") 229 | options.add_argument("--disable-infobars") 230 | if self.__headless: 231 | driver = Chrome(options=options, enable_console_log=False, enable_logging=False, 232 | version_main=version_main, headless=True) 233 | else: 234 | driver = Chrome(options=options, enable_console_log=False, enable_logging=False, 235 | version_main=version_main) 236 | return driver 237 | 238 | def get_browser(self) -> WebDriver: 239 | """ 240 | Get the WebDriver object for the current browser session. 241 | Returns: 242 | WebDriver: The WebDriver object. 243 | """ 244 | 245 | if not self.__current_driver: 246 | self.__current_driver = self.__created_patched_driver() 247 | return self.__current_driver 248 | 249 | def __skip_old_login_page(self) -> None: 250 | """ 251 | Skip the old Yahoo login page if present. 252 | Returns: 253 | None 254 | """ 255 | 256 | try: 257 | self.__current_driver.find_element(By.CLASS_NAME, self.__class_new_account).click() 258 | except NoSuchElementException: 259 | ... 260 | 261 | def __insert_email(self, email: str, pace: float = .5) -> bool: 262 | """ 263 | Insert the Yahoo email address into the login form. 264 | 265 | Parameters: 266 | - email (str): Yahoo email address. 267 | - pace (float): Typing pace for entering characters. 268 | 269 | Returns: 270 | bool: True if successful, False otherwise. 271 | """ 272 | 273 | self.__pprints_override(status=f"Performing email address operation on: {email}") 274 | actions: ActionChains = ActionChains(driver=self.__current_driver) 275 | try: 276 | email_block = self.__current_driver.find_element(by=By.ID, value=self.__id_username) 277 | email_block.click() 278 | except NoSuchElementException: 279 | return False 280 | for char in email: 281 | actions.send_keys(char) 282 | actions.perform() 283 | sleep(pace) 284 | sleep(.4) 285 | try: 286 | self.__current_driver.find_element(by=By.NAME, value=self.__name_email_signin).click() 287 | except NoSuchElementException: 288 | return False 289 | sleep(self.__short_wait) 290 | return True 291 | 292 | def __check_yahoo_first_time_ban(self) -> tuple[bool, str]: 293 | """ 294 | Check if the Yahoo account is first-time banned. 295 | 296 | Returns: 297 | tuple[bool, str]: Tuple indicating ban status and status message. 298 | """ 299 | 300 | self.__pprints_override(status="Checking yahoo ban traces") 301 | try: 302 | banned_timer_text: str = self.__current_driver.find_element(by=By.ID, value=self.__id_banned_time).text 303 | ban_remaining_time: any([None, str]) = search(pattern=self.__banned_time_exp, string=banned_timer_text) 304 | if ban_remaining_time: 305 | minutes = int(ban_remaining_time.group(1)) 306 | error_text: str = f"Your ip has been baned and unblock after: {minutes} minutes." 307 | self.__pprints_override(status=error_text) 308 | return False, error_text 309 | else: 310 | return False, banned_timer_text 311 | except NoSuchElementException: 312 | return True, "" 313 | 314 | def __solve_captcha(self) -> tuple[bool, str]: 315 | """ 316 | Solve the reCAPTCHA challenge if present. 317 | 318 | Returns: 319 | tuple[bool, str]: Tuple indicating a success status and status message. 320 | """ 321 | 322 | self.__pprints_override(status="Checking for captcha") 323 | try: 324 | captcha_block_iframe = self.__current_driver.find_element(by=By.CSS_SELECTOR, 325 | value=self.__css_recaptcha_iframe_1) 326 | self.__current_driver.switch_to.frame(frame_reference=captcha_block_iframe) 327 | recaptcha_iframe = self.__current_driver.find_element(by=By.XPATH, value=self.__xpath_recaptcha_iframe_2) 328 | captcha_solver = RecaptchaSolver(driver=self.__current_driver) 329 | try: 330 | captcha_solver.click_recaptcha_v2(iframe=recaptcha_iframe) 331 | except TimeoutException: 332 | return True, "" 333 | except RecaptchaException: 334 | self.__current_driver.switch_to.default_content() 335 | return False, "Recaptcha solver is detected" 336 | self.__current_driver.switch_to.default_content() 337 | try: 338 | self.__current_driver.find_element(by=By.ID, value=self.__id_captcha_submit_btn).click() 339 | except NoSuchElementException: 340 | return False, "Does not able to find captcha submit button" 341 | return True, "" 342 | except NoSuchElementException: 343 | return True, "" 344 | 345 | def __insert_password(self, password: str, pace: float = 0.5) -> bool: 346 | """ 347 | Insert the password into the login form. 348 | 349 | Parameters: 350 | - password (str): Password to be entered. 351 | - pace (float): Typing pace for entering characters. 352 | 353 | Returns: 354 | bool: True if successful, False otherwise. 355 | """ 356 | 357 | self.__pprints_override(status=f"Performing password address operation on: {password}") 358 | actions: ActionChains = ActionChains(driver=self.__current_driver) 359 | try: 360 | password_block = self.__current_driver.find_element(by=By.ID, value=self.__id_password) 361 | password_block.click() 362 | except NoSuchElementException: 363 | return False 364 | for char in password: 365 | actions.send_keys(char) 366 | actions.perform() 367 | sleep(pace) 368 | sleep(.4) 369 | try: 370 | self.__current_driver.find_element(by=By.ID, value=self.__id_password_signin).click() 371 | except NoSuchElementException: 372 | return False 373 | sleep(self.__short_wait - 2 if self.__short_wait - 3 > 0 else 2) 374 | return True 375 | 376 | def __is_password_correct(self) -> bool: 377 | """ 378 | Check if the last-entered password is correct. 379 | 380 | Returns: 381 | bool: True if correct, False otherwise. 382 | """ 383 | 384 | self.__pprints_override(status="Checking if last password is correct") 385 | try: 386 | error_message: str = self.__current_driver.find_element(by=By.CLASS_NAME, 387 | value=self.__class_password_error).text.strip() 388 | if "invalid password" in error_message.lower(): 389 | return False 390 | return True 391 | except NoSuchElementException: 392 | return True 393 | 394 | def __is_unexpected_error(self) -> tuple[bool, str]: 395 | """ 396 | Check if there is an unexpected error during the login process. 397 | 398 | Returns: 399 | tuple[bool, str]: Tuple indicating error status and error message. 400 | """ 401 | 402 | try: 403 | error_notification: str = self.__current_driver.find_element( 404 | by=By.CLASS_NAME, value=self.__class_unexpected_error 405 | ).text.strip() 406 | return True, f"Unsolvable error: {error_notification}" 407 | except NoSuchElementException: 408 | return False, "" 409 | 410 | def login_to_yahoo(self, yahoo_mail: str, passwords_combo: list[str], first_run: bool = True, 411 | pace: float = 0.5) -> tuple[bool, str]: 412 | """ 413 | Log in to a Yahoo account using provided credentials. 414 | Parameters: 415 | - yahoo_mail (str): Yahoo email address. 416 | - passwords_combo (list[str]): List of passwords to try. 417 | - first_run (bool): Flag indicating if it's the first run. 418 | - pace (float): Typing pace for email and password. 419 | Returns: 420 | tuple[bool, str]: Tuple indicating a success status and status message. 421 | """ 422 | 423 | if not self.__current_driver: 424 | self.__current_driver = self.__created_patched_driver() 425 | 426 | self.__current_driver.get(url=self.__TARGET_SITE) 427 | if first_run: 428 | self.__pprints_override(status="Loading predefined cookies") 429 | pre_defined_cookies = load(file=open(self.__predefined_cookies, 'rb')) 430 | for cookie in pre_defined_cookies: 431 | self.__current_driver.add_cookie(cookie) 432 | self.__current_driver.refresh() 433 | self.__skip_old_login_page() 434 | sleep(self.__estimated_wait) 435 | if not self.__insert_email(email=yahoo_mail, pace=pace): 436 | return False, "An error occurred while performing the email entering function." 437 | 438 | ban_status: tuple[bool, str] = self.__check_yahoo_first_time_ban() 439 | if not ban_status[0]: 440 | return ban_status 441 | captcha_checks: tuple[bool, str] = self.__solve_captcha() 442 | if not captcha_checks[0]: 443 | return captcha_checks 444 | 445 | for password in passwords_combo: 446 | sleep(self.__short_wait - 3 if self.__short_wait - 3 > 0 else 2) 447 | if not self.__insert_password(password=password, pace=pace): 448 | return False, "An error occurred while performing the password entering function." 449 | if self.__is_password_correct(): 450 | self.__pprints_override(status=f"Password matched; account is logged in using: {password}") 451 | self.__founded_password = password 452 | return True, f"Password matched; account is logged in using: {password}" 453 | unexpected_error: tuple[bool, str] = self.__is_unexpected_error() 454 | if unexpected_error[0]: 455 | self.__pprints_override(status=unexpected_error[1]) 456 | return False, unexpected_error[1] 457 | captcha_checks: tuple[bool, str] = self.__solve_captcha() 458 | if not captcha_checks[0]: 459 | return captcha_checks 460 | --------------------------------------------------------------------------------