diff --git a/.github/var b/.github/var index 6f37f42..61877db 100644 --- a/.github/var +++ b/.github/var @@ -1,5 +1,10 @@ -whatsapp_web_url: https://web.whatsapp.com/ -mBox: /html/body/div/div/div/div[4]/div/footer/div[1]/div[2]/div/div[2] -mainScreenLoaded: ._3FRCZ -searchSelector: .cBxw- > div:nth-child(2) +whatsapp_web_url: 'https://web.whatsapp.com/' +messageBox: '/html/body/div/div/div/div[4]/div/footer/div[1]/div[2]/div/div[2]' +mainScreenLoaded: '._3FRCZ' +searchSelector: '.cBxw- > div:nth-child(2)' +person: '//*[@title="name"]' +attach: '//span[@data-icon="clip"]' +send: '//span[@data-icon="send"]' +media: '//input[@accept="image/*,video/mp4,video/3gpp,video/quicktime"][@type="file"]' +file: '//input[@accept="*"]' # Aahnik 2020 diff --git a/.github/ver b/.github/ver index b8626c4..7ed6ff8 100644 --- a/.github/ver +++ b/.github/ver @@ -1 +1 @@ -4 +5 diff --git a/README.md b/README.md index ba1a797..c15db4b 100644 --- a/README.md +++ b/README.md @@ -3,33 +3,30 @@ Wondering how to send WhatsApp messages using Python using only few lines of code? You have come to the right place! [![Tests](https://img.shields.io/badge/tests-passing-green)](https://aahnik.github.io/wappdriver/docs/Tests.html) +[![Maintenance](https://img.shields.io/maintenance/yes/2020)](https://github.com/aahnik/wappdriver/graphs/commit-activity) +[![GitHub Release](https://img.shields.io/github/v/release/aahnik/wappdriver)](https://github.com/aahnik/wappdriver/releases) +[![CodeFactor](https://www.codefactor.io/repository/github/aahnik/wappdriver/badge)](https://www.codefactor.io/repository/github/aahnik/wappdriver) _`wappdriver` enables you to send WhatsApp messages programmatically, using only 3 lines of code._ **A python package that helps you automate sending messages through WhatsApp Web 😎** -[![Maintenance](https://img.shields.io/maintenance/yes/2020)](https://github.com/aahnik/wappdriver/graphs/commit-activity) -[![GitHub Release](https://img.shields.io/github/v/release/aahnik/wappdriver)](https://github.com/aahnik/wappdriver/releases) -[![CodeFactor](https://www.codefactor.io/repository/github/aahnik/wappdriver/badge)](https://www.codefactor.io/repository/github/aahnik/wappdriver) - +### *🌟🌟 New* +WappDriver now supports sending **images** , **videos**, **documents** and other file types... *[learn usage](https://aahnik.github.io/wappdriver/docs/Documentation.html)* + ### It's very simple to use -```python -import wappdriver as wa -bot = wa.WappDriver() -bot.send_message(to='aahnik',msg='Hi ! sent by a bot :-p ') -# the recipients name must be saved in your contacts ... - -``` -![wapp_driver_scrnsht](https://user-images.githubusercontent.com/66209958/90502857-2879a600-e16c-11ea-8f7f-7bbf2a993a13.png) +![using wappdriver](docs/images/wappdriver.png). ### How to install ?? ``` -pip3 install wappdriver +pip install wappdriver ``` +For Mac and Linux, you may need to use `pip3`. + [PyPI](https://pypi.org/project/wappdriver/) [![MIT LICENSE](https://img.shields.io/pypi/l/ansicolortags.svg)](/LICENSE) @@ -42,8 +39,6 @@ So **make sure to close any chrome tab which has WhatsApp Web open**. Not doing - Your phone which is having that WhatsApp account, must stay connected to internet for WhatsApp Web to work - - ### Requirements [![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg)](https://www.python.org/) @@ -58,24 +53,17 @@ I recommend to use the latest version of both, for the best performance. ### Documentation -[Read](https://aahnik.github.io/wappdriver/docs/Documentation.html) the full Documentation. - -### Tests - -[Learn more](https://aahnik.github.io/wappdriver/docs/Tests.html) about testing. - +[Read](https://aahnik.github.io/wappdriver/docs/Documentation.html) the full Documentation to know about all the features. ### Want to contribute ? Please look at [Code of Conduct](https://github.com/aahnik/wappdriver/blob/master/.github/CODE_OF_CONDUCT.md#contributor-covenant-code-of-conduct) and [Contributing Guidelines](https://github.com/aahnik/wappdriver/blob/master/.github/CONTRIBUTING.md#how-to-contribute-to-wappdriver-) +Please read the explanation of the detailed working of `wappdriver` from the [Developer's Guide.](https://aahnik.github.io/wappdriver/docs/For_Developers.html) -### Special Thanks to @VISWESWARAN1998 -I learned a lot from his [repo](https://github.com/aahnik/Simple-Yet-Hackable-WhatsApp-api) and had initially started working on it. In future, due to certain requirements, I created a seperate repo, with a different working all together. - ### Help For _help_ you can **email me** at [meet.aahnik@gmail.com](mailto:meet.aahnik@gmail.com) or **chat** on [Telegram](https://t.me/AahnikDaw).You can expect a reply within 48 hours. diff --git a/docs/Documentation.md b/docs/Documentation.md index feae67f..04358b3 100644 --- a/docs/Documentation.md +++ b/docs/Documentation.md @@ -3,6 +3,41 @@ This is a detailed documentation of the functioning of `wappdriver`. [README](https://aahnik.github.io/wappdriver) for introduction. - [Documentation](#documentation) + - [First time Setup](#first-time-setup) + - [Sending Images, Videos, Documents and other Files](#sending-images-videos-documents-and-other-files) + - [Sending Multiple Files Together](#sending-multiple-files-together) + - [Sending Messages with Files](#sending-messages-with-files) +## First time Setup +There is no hassle in setting up `wappdriver`. +>Make sure you have matching versions of Chrome and Chrome Driver. +After you `pip install wappdriver`, you can directly run your code. +- You will be prompted to enter the installation path of Chrome Driver Executable (only once). Just copy and paste the correct path there. +- When you load WhatsApp for the first time, you have to scan the QR Code that will be displayed on your computer from your phone, to login into WhatsApp Web + + +## Sending Images, Videos, Documents and other Files + +With `wappdriver` its easy to do all these. Simply pass the path of the image or video or document file to the `send` method. You must use absoulte file paths. +You can send any file type that WhatsApp supports. +> WhatsApp does not support files over 16 MB + +![sending an image](images/sending_media.png) + +## Sending Multiple Files Together + +You can easily pass as many arguments to `send` method of `WhatsApp` class you wish. +**Just remember that the first argument must be the name of the recipient.** + +![sending multiple files](images/sending_multiple_files.png) + + +## Sending Messages with Files + +Hmm! `wappdriver` is extremely smart. It can detect whether a string is a message or a file path. So you can do this as shown below. Dont hesitate to use a multiline line string for a long message. + +![files and messages](images/files_and_messages.png) + +The messages will be send in exactly that order. \ No newline at end of file diff --git a/docs/Tests.md b/docs/Tests.md deleted file mode 100644 index 7343b33..0000000 --- a/docs/Tests.md +++ /dev/null @@ -1,39 +0,0 @@ -# Testing - -Tests are very important for any application. -Jacob Kaplan-Moss said -> "Code without tests is broken by design". - -- [Testing](#testing) - - [Introduction](#introduction) - - [Testing : Why not fully automated ?](#testing--why-not-fully-automated-) - - ---- - - -## Introduction -We have multiple tests to check whether this application is _working as expected_. But these tests need certain _user input_ to run such as names of people saved in your WhatsApp contacts. ( To avoid spamming, this application *allows sending messages only to your WhatsApp contacts* ). - -__I run the tests on a regular basis and manually update the feature badges__ at the top of this README, to indicate whether that particular feature is successfully working or not. - -If you find that any of the features is not working as expected, _feel free to create an issue_. Nonetheless, you could easily *clone this repo* and **run the tests locally** after configuring the variables in `testConfig.yml` file inside `test_wappdriver` directory. - - -## Testing : Why not fully automated ? - -These tests __could not be automatically run__ on server via Travis CI or GitHub Actions due to certain constraints due to the nature of WhatsApp and this application. -These tests could not be automatically run on server via Travis CI or GitHub Actions due to certain constraints as mentioned below: - - -- Logging in to WhatsApp requires running the GUI Chrome, for scanning QR Codes, thus logging in could not be done in server. -- I thought of logging in from my computer, and then upload the cookies, so that the test runner could login and perform actions in headless mode. But that would compromise my security. As far as I know we cant upload an entire folder to GitHub secrets. There may be certian work-arounds which I might not have discovered or may be I was too lazy to implement those. -- WhatsApp allows you to be logged in only from one Chrome Tab. So if the tester is logged in, to keep it logged in, I have to sacrifice using WhatsApp from my web. -- Currently I have two WhatsApp numbers, one for personal use and the other for business use. To login from WhatsApp Web, your phone must have that WhatsApp account actively logged in and the phone must be connected to the internet. I have only one phone (ultra low spec) ( I know there can be work-around, but that would increase the complexity of my life. - - - - - -To Do : add instructions - diff --git a/docs/images/files_and_messages.png b/docs/images/files_and_messages.png new file mode 100644 index 0000000..abfcd8b Binary files /dev/null and b/docs/images/files_and_messages.png differ diff --git a/docs/images/sending_media.png b/docs/images/sending_media.png new file mode 100644 index 0000000..c0588f2 Binary files /dev/null and b/docs/images/sending_media.png differ diff --git a/docs/images/sending_multiple_files.png b/docs/images/sending_multiple_files.png new file mode 100644 index 0000000..f78d98e Binary files /dev/null and b/docs/images/sending_multiple_files.png differ diff --git a/docs/images/wappdriver.png b/docs/images/wappdriver.png new file mode 100644 index 0000000..f696e62 Binary files /dev/null and b/docs/images/wappdriver.png differ diff --git a/requirements.txt b/requirements.txt index e90a358..1637699 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,19 +3,10 @@ certifi==2020.6.20 chardet==3.0.4 idna==2.10 pycodestyle==2.6.0 -PyYAML==5.3.1 -requests==2.24.0 -selenium==3.141.0 -toml==0.10.1 -urllib3==1.25.10 -autopep8==1.5.4 -certifi==2020.6.20 -chardet==3.0.4 -idna==2.10 -pycodestyle==2.6.0 pyfiglet==0.8.post1 PyYAML==5.3.1 requests==2.24.0 selenium==3.141.0 toml==0.10.1 -urllib3==1.25.10 \ No newline at end of file +tqdm==4.49.0 +urllib3==1.25.10 diff --git a/setup.py b/setup.py index f85c969..e37f128 100644 --- a/setup.py +++ b/setup.py @@ -19,7 +19,7 @@ long_description=long_description, long_description_content_type="text/markdown", url="https://github.com/aahnik/wappdriver", - install_requires=['selenium', 'pyyaml', 'requests', 'pyfiglet','pytest'], + install_requires=['selenium', 'pyyaml', 'requests', 'pyfiglet','tqdm'], include_package_data=True, zip_safe=False, entry_points={ diff --git a/wappdriver/__init__.py b/wappdriver/__init__.py index 6ad2095..66fb08f 100644 --- a/wappdriver/__init__.py +++ b/wappdriver/__init__.py @@ -1,6 +1,8 @@ -__version__ = "0.3.2" +__version__ = "0.5.0.beta" -from .driver import WappDriver +# from .context import Wapp from .data.local import set_chrome_driver_path, update_vars - +from .error import handle_errors +from .driver import WappDriver +from .whatsapp import category,WhatsApp # AAHNIK 2020 diff --git a/wappdriver/command_line.py b/wappdriver/command_line.py index a6c2bb5..2d266b4 100644 --- a/wappdriver/command_line.py +++ b/wappdriver/command_line.py @@ -1,17 +1,26 @@ from . import __version__ import argparse -from pyfiglet import Figlet +try: + from pyfiglet import Figlet +except Exception: + print('''Could not find pyfiglet + Run + pip install pyfiglet + ''') def main(): parser = argparse.ArgumentParser() args = parser.parse_args() - print(f'wappdriver: {__version__}') + print(f''' + wappdriver: {__version__} + Command Line App is not availaible. + Please Update wappdriver + ''') f = Figlet(font='big') print(f.renderText('wappdriver')) - if __name__ == "__main__": main() diff --git a/wappdriver/data/data_error.py b/wappdriver/data/data_error.py new file mode 100644 index 0000000..2219907 --- /dev/null +++ b/wappdriver/data/data_error.py @@ -0,0 +1,51 @@ +''' +The errors that occur during Data Handling are handled by the decorators defined in this module +''' + + +def handle_connection(func): + def wrapper_func(*args): + try: + return func() + except Exception as err: + print(f''' + ----------------------------------- + 😞 + + Could not fetch data from Internet. + Please check your connection + + {err} + + For help visit + https://aahnik.github.io/wappdriver/docs/help.html + ----------------------------------- + ''') + quit() + return wrapper_func + + +def handle_dependancy(func): + def wrapper_func(*args): + try: + return func() + except Exception as err: + print(f''' + ----------------------------------- + 😞 + + Dependancies Missing + + {err} + + Please run + pip install pyyaml requests + + And then try again + + For help visit + https://aahnik.github.io/wappdriver/docs/help.html + ----------------------------------- + ''') + quit() + return wrapper_func diff --git a/wappdriver/data/local.py b/wappdriver/data/local.py index 78b4798..fa7292e 100644 --- a/wappdriver/data/local.py +++ b/wappdriver/data/local.py @@ -5,12 +5,17 @@ --- Updates can be performed manually by calling `update_vars()` ''' - +from .. import __version__ +from datetime import datetime +from .data_error import handle_dependancy from . import remote import os -import yaml -from datetime import datetime -from .. import __version__ + + +@handle_dependancy +def yml(): + import yaml + return yaml wapp_dir = os.path.expanduser('~/.wappdriver') @@ -21,7 +26,8 @@ version_file = os.path.join(wapp_dir, 'ver.txt') -sessions_dir = os.path.join(wapp_dir,'sessions') +sessions_dir = os.path.join(wapp_dir, 'sessions') + def set_chrome_driver_path(path=''): ''' @@ -80,7 +86,7 @@ def get_local_vars(): Returns the vars dictionary ''' with open(var_file, 'r') as f: - return yaml.full_load(f) + return yml().full_load(f) def set_local_ver(ver): @@ -123,7 +129,8 @@ def update_vars(): return True except Exception as e: - print(f'Could not update data from Internet. Check your internet connection \n {e}') + print( + f'Could not update data from Internet. Check your internet connection \n {e}') def ensure(): diff --git a/wappdriver/data/remote.py b/wappdriver/data/remote.py index 6fef5d8..1de242a 100644 --- a/wappdriver/data/remote.py +++ b/wappdriver/data/remote.py @@ -1,6 +1,13 @@ '''This module remote.py provides access to remote. It fetches latest data from the internet''' -import requests +from .data_error import handle_connection, handle_dependancy + + +@handle_dependancy +def rqst(): + import requests + return requests + prefix = 'https://raw.githubusercontent.com/aahnik/wappdriver/main/.github/' @@ -8,15 +15,21 @@ var_url = f'{prefix}var' +@handle_connection def version(): ''' Returns remote version ''' - return float(requests.get(url=version_url).text) + ver = float(rqst().get(url=version_url).text) + return ver +@handle_connection def fetch_vars(): ''' Returns the content of remote vars as a string ''' - return requests.get(url=var_url).text + dynamic_vars = rqst().get(url=var_url).text + return dynamic_vars + + diff --git a/wappdriver/driver.py b/wappdriver/driver.py index e538744..bc02899 100644 --- a/wappdriver/driver.py +++ b/wappdriver/driver.py @@ -1,19 +1,28 @@ -'''This module driver.py contains the WappDriver class, which is responsible for driving the core -features of the application. +'''This module driver.py contains the WappDriver class, which is responsible for driving the core features of the application. You have to create an instance of the WappDriver class, to do any meaningful activity such as sending a text message or media(Image/GIF/Video) or PDF document ''' -from wappdriver.error import WappDriverError -from . import error # when error is imported local is ensured -from .data import local +from selenium.webdriver.common import keys +from .error import handle_errors +from .data.local import get_chrome_driver_path, get_local_vars, sessions_dir + +import time import os -from selenium import webdriver -from selenium.webdriver.chrome.options import Options -from selenium.webdriver.support.ui import WebDriverWait -from selenium.webdriver.common.by import By -from selenium.webdriver.support import expected_conditions -from selenium.webdriver.common.keys import Keys + +try: + from selenium import webdriver + from selenium.webdriver.chrome.options import Options + from selenium.webdriver.support.ui import WebDriverWait + from selenium.webdriver.common.by import By + from selenium.webdriver.support import expected_conditions + from selenium.webdriver.common.keys import Keys + +except Exception: + print(f'''Could not find Selenium + Run + pip install selenium + ''') class WappDriver(): @@ -21,148 +30,64 @@ class WappDriver(): It interacts with the webdriver to send messages ''' - def __init__(self, session='default', timeout=50): - - self.chrome_driver_path = local.get_chrome_driver_path() - - _var = local.get_local_vars() - - self.whatsapp_web_url = _var['whatsapp_web_url'] - self.mainScreenLoaded = _var['mainScreenLoaded'] - self.searchSelector = _var['searchSelector'] - self.mBox = _var['mBox'] - - # the webdriver waits for an element to be detected on screen on until timeout + def __init__(self, timeout): + self.chrome_driver_path = get_chrome_driver_path() + self._var = get_local_vars() self.timeout = timeout + self.last_person = '' - if self.load_chrome_driver(session): - if self.load_main_screen(): - print("Yo!! sucessfully loaded WhatsApp Web") - else: - self.driver.quit() - + @handle_errors('ChromeDriver not loaded', 'correct version of Chrome and Chrome Driver') def load_chrome_driver(self, session): - session_path = os.path.join(local.sessions_dir, session) - - try: - chrome_options = Options() - chrome_options.add_argument(f'--user-data-dir={session_path}') - - self.driver = webdriver.Chrome( - options=chrome_options, executable_path=self.chrome_driver_path) - - return True - - except Exception as internal: - try: - raise WappDriverError(internal, - problem='Chrome Driver could not be successfuly loaded', - message='Make sure to have correct version of Chrome and Chrome Driver') - except Exception as err: - print(err) - return False + session_path = os.path.join(sessions_dir, session) + chrome_options = Options() + chrome_options.add_argument(f'--user-data-dir={session_path}') + self.driver = webdriver.Chrome( + options=chrome_options, executable_path=self.chrome_driver_path) + @handle_errors('WhatsApp not loaded', 'correct version of Chrome and Chrome Driver') def load_main_screen(self): - try: - self.driver.get(self.whatsapp_web_url) - - WebDriverWait(self.driver, self.timeout).until( - expected_conditions.presence_of_element_located((By.CSS_SELECTOR, self.mainScreenLoaded))) - return True - - except Exception as internal: - try: - raise WappDriverError(internal, - problem='WhatsApp main screen could not be successfuly loaded', - message='Make sure to have correct version of Chrome and Chrome Driver') - except Exception as err: - print(err) - return False - - # selecting a person after searching contacts - - def load_person(self, name): + self.driver.get(self._var['whatsapp_web_url']) + WebDriverWait(self.driver, self.timeout).until( + expected_conditions.presence_of_element_located((By.CSS_SELECTOR, self._var['mainScreenLoaded']))) + @handle_errors('Person search failed', '') + def search_person(self, name): search_box = self.driver.find_element_by_css_selector( - self.searchSelector) - - # we will send the name to the input key box + self._var['searchSelector']) + search_box.send_keys(Keys.CONTROL+Keys.BACK_SPACE) search_box.send_keys(name) - try: - person = WebDriverWait(self.driver, self.timeout).until(expected_conditions.presence_of_element_located( - (By.XPATH, f'//*[@title="{name}"]'))) - person.click() - return True - - except Exception as error: - message = f'''{name} not loaded, MAY BE NOT IN YOUR CONTACTS , - If you are sure {name} is in your contacts, Try checking internet connection - - OR May be some other problem ... ''' - - search_box.send_keys((Keys.BACKSPACE)*len(name)) - # clearing the search bar by backspace, so that searching the next person does'nt have any issue - return False - - def send_message(self, to, msg): - '''Method to send a text message to a contact. - Requires two arguments: to and msg - - Example Use : - bot.send_message(to='contact',msg='hi') - or - bot.send_message('contact','hi') - where bot is an object of WappDriver class - ''' - - if self.load_person(to): - msg_box = WebDriverWait(self.driver, self.timeout).until( - expected_conditions.presence_of_element_located((By.XPATH, self.mBox))) - lines = msg.split('\n') - - for line in lines: - msg_box.send_keys(line) # write a line - msg_box.send_keys(Keys.SHIFT + Keys.ENTER) # go to next line - + @handle_errors(problem='Person not loaded', message='person in your contacts') + def load_person(self, name): + if name != self.last_person: + if self.search_person(name): + person_button = WebDriverWait(self.driver, self.timeout).until( + expected_conditions.presence_of_element_located( + (By.XPATH, self._var['person'].replace('name', name)))) + person_button.click() + self.last_person = name + + @handle_errors('Message not sent', 'no invalid emojis') + def send_text(self, msg): + msg_box = WebDriverWait(self.driver, self.timeout).until( + expected_conditions.presence_of_element_located((By.XPATH, self._var['messageBox']))) + lines = msg.split('\n') + for line in lines: + msg_box.send_keys(line) # write a line + msg_box.send_keys(Keys.SHIFT + Keys.ENTER) # go to next line msg_box.send_keys(Keys.ENTER) # send message - return True - - def send_media(self, to, path, caption=None): - '''Method to send a media object to a contact. - Supports: Image, GIF, Video - Requires two arguments: to and path - Optional argument: caption - - Example Use : - bot.send_media(to='contact',path='path/to/media',caption='wow') - or - bot.send_media('contact','path/to/media','wow') - where bot is an object of WappDriver class - - Not giving the caption is allowed - ''' - - if self.load_person(to): - pass - - def send_file(self, to, path): - '''Method to send any kind of file to a contact. - Supports: ALl formats - Requires two arguments: to and path - - Example Use : - bot.send_file(to='contact',path='path/to/file') - or - bot.send_file('contact','path/to/file') - where bot is an object of WappDriver class - - Not giving the caption is allowed - ''' - - def send_contact(self, to, contact): - pass - - def send_url(self, to, url): - pass + @handle_errors('Button click failed', '') + def click_button(self, button): + button = WebDriverWait(self.driver, self.timeout).until( + expected_conditions.presence_of_element_located(( + By.XPATH, self._var[button]))) + button.click() + + @handle_errors('Could not send file', 'the file you are trying to send, and you must use absolute file path') + def send_file(self, path, inp_categ, caption=None): + if self.click_button('attach'): + input_field = WebDriverWait(self.driver, self.timeout).until( + expected_conditions.presence_of_element_located((By.XPATH, self._var[inp_categ]))) + input_field.send_keys(path) + self.click_button('send') diff --git a/wappdriver/error.py b/wappdriver/error.py index fc41af0..d25ff40 100644 --- a/wappdriver/error.py +++ b/wappdriver/error.py @@ -3,13 +3,16 @@ WappDriver Excpetion helps in abstracting internal exception details from the end user --- -This module can be used by any other module except `local` from `data` subpackage -''' +`local` and `remote` modules from `data` subpackage, should not import `error` in order to +prevent cyclic import +When you import this module or any function from this module, local is ensured 😊 +''' +import functools from .data import local import logging -local.ensure() # to prevent errors and first time setup +local.ensure() # to prevent errors and first time setup logging.basicConfig(format='\n########################################\n\n%(asctime)s - %(message)s', filename=local.log_file) @@ -19,35 +22,21 @@ class WappDriverError(Exception): ''' Exception raised for errors in functioning of WappDriver. - Attributes: - internal - - actual exceptions and full traceback which are being abstracted away from end user - problem - - what could have went wrong according to the developer - message -- custom message explainng what could have caused the error and how to resolve it - - ---------- - - Use : When any error is caught in WappDriver, an WappDriverError is raised. - - ```python + --- + ### Attributes - try: - d = 2/0 - # code that might trigger some error - - except Exception as internal: - try: - raise WappDriverError(internal=internal, - problem="could not load this / this failed ...", - message="check that ... / have you ..? ") - except Exception as err: - print(err) - ``` + internal - - actual exceptions and full traceback which are being abstracted away from end user - Raising WappDriverError automatically logs the entire internal error messages with timestamps. - The logs are not displayed to the user, rather they are appended to the log file. + problem - - what could have went wrong according to the developer + message -- custom message explainng what could have caused the error and how to resolve it. When displayed, message is prefixed with the words `Make sure to have:` + --- + - The `problem` and current time will be the heading of any particular log + - `internal` is not shown to the user, but logged. + --- + Raising an WappDriverError automatically logs the entire internal error messages with timestamps.The logs are not displayed to the user, rather they are appended to the log file. ''' def __init__(self, internal, problem, message): @@ -60,12 +49,64 @@ def __init__(self, internal, problem, message): def __str__(self): return f''' + Please check Internet Connection and always use the latest version of wappdriver. ------------------------------------------------------------------------------------ - WappDriver Error Occured - - {self.message} - - Please make sure to use the latest version of wappdriver + WappDriver Error : {self.problem} \n + Make sure to have : {self.message} \n For Help Visit https://aahnik.github.io/wappdriver/docs/help.html -----------------------------------------------------------------------------------\n ''' + + +def handle_errors(problem, message): + ''' + ### How to use ? + Write the vulnerable code inside a function. And decorate it with `handle_error`. There should be no `return` statement inside the vulnerable function. The intended use case is for a procedural function. + + --- + Example use + ```python + # Note: when displayed, message is prefixed with the words `Make sure to have:` + @handle_errors(problem='this not done',message='that') + def vulnerable_function(arg): + print('fishy errors') + print(1/0) + ``` + --- + #### After being decorated, the vulnerable function will return `True` or `False` to the caller + + - `True` will be returned in case of no error + - If an error is caught it will be handled and beautified message will be displayed to user + - The internal details of the Exception and full traceback will be logged. + - `False` will be returned to the caller. + + --- + `handle_errors` is a Decorator Factory which is used to create a decorator that takes arguments + ''' + + def decorator_func(vulnerable_func): + ''' + Actual Decorator to handle errors. It is nested inside a decorator factory, + as this decorator needs to take two arguments. + + ''' + @functools.wraps(vulnerable_func) + def wrapper_func(*args, **kwargs): + ''' + Wrapper Function to wrap vulnerable functions in WappDriver. + This is internally used by WappDriver. + + Not intended to be used by users of WappDriver + ''' + try: + vulnerable_func(*args, **kwargs) + return True # when the vulnerable code does not raise any error + except Exception as internal: + try: + raise WappDriverError(internal, problem, message) + except Exception as err: + # Beautified Error message is printed and False is returned to caller + print(err) + return False + return wrapper_func + return decorator_func diff --git a/wappdriver/tests/__init__.py b/wappdriver/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/wappdriver/tests/testConfig.yml b/wappdriver/tests/testConfig.yml deleted file mode 100644 index 954a293..0000000 --- a/wappdriver/tests/testConfig.yml +++ /dev/null @@ -1,37 +0,0 @@ -# PLease read the Documentation regarding testing, before attempting to test -# this is a yaml file, so no need to enclose strings by double/single quotes - -# please make sure to change the values of the parameters before testing - -absolute_WhatsApp_Session_Folder_Path: -# if not logged in, QR code will be displayed - - -saved_contact: aahnik -# case sensitive - - -unsaved_contact: fakeNobody -# name that is not saved -# this is required for checking whether the program successfully raises -# wappdriver error for UnknownContact - - -# note: -# the media at the paths you provide below, will be send to the saved_contact specified above - - -absolute_ImagePath: path/to/ -# the absolute path to any image on your computer - - -absolute_VideoPath: path/to/ -# the absolute path to any Video on your computer - - -absolute_GIFPath: path/to/ -# the absolute path to any GIF on your computer - - -absolute_PDFPath: path/to/ -# the absolute path to any PDF on your computer \ No newline at end of file diff --git a/wappdriver/tests/test_wappdriver.py b/wappdriver/tests/test_wappdriver.py deleted file mode 100644 index c671b12..0000000 --- a/wappdriver/tests/test_wappdriver.py +++ /dev/null @@ -1,27 +0,0 @@ -''' -Tests the core features of WappDriver -''' -import pytest - - -@pytest.fixture -def bot(): - from .. import WappDriver - bot = WappDriver() - return bot - - -def test_text(bot): - assert bot.send_message(to='aahnik', msg='success') - - -def test_media(): - pass - - -def test_url(): - pass - - -def test_contact(): - pass diff --git a/wappdriver/whatsapp.py b/wappdriver/whatsapp.py new file mode 100644 index 0000000..45e2354 --- /dev/null +++ b/wappdriver/whatsapp.py @@ -0,0 +1,67 @@ +''' +This module contains the context manager for creating a bot ie an instance of WappDriver Class +''' + +from .driver import WappDriver +import os +from tqdm import tqdm +import time + + +def category(arg): + ''' + Returns a string (or None if invalid) + + - `text` for plain text or url + - `media` for path of image or video file path + - `file` for path of any other file type + + ''' + + if os.path.isfile(arg): + if os.stat(arg).st_size <= 1.6e+7: + file_type = os.path.splitext(arg)[1] + if file_type in ['.png', '.jpg', '.jpeg', '.gif', '.mp4']: + return 'media' + else: + return 'file' + else: + print('Files of more than 16 MB are not allowed in WhatsApp') + quit() + + return 'text' + + +class WhatsApp(): + + def __init__(self, timeout=50, session='default'): + self.timeout = timeout + self.session = session + + def __enter__(self): + with tqdm(total=10) as progress: + self.wappdriver = WappDriver(self.timeout) + progress.update(3) + if self.wappdriver.load_chrome_driver(self.session): + progress.update(3) + if self.wappdriver.load_main_screen(): + progress.update(4) + return self + self.wappdriver.driver.close() + + def send(self, to, *args): + if self.wappdriver.load_person(to): + with tqdm(total=len(args)) as progress: + for arg in args: + if category(arg) == 'text': + self.wappdriver.send_text(arg) + else: + self.wappdriver.send_file(arg, category(arg)) + size_mb = os.stat.st_size(arg)*1e-6 + time.sleep((self.timeout/10)*size_mb) + progress.update(1) + + def __exit__(self, exception_type, exception_value, traceback): + time.sleep(30) + print('Closing WhatsApp') + self.wappdriver.driver.close()