Repository: whatsplay/whatsapp-play Branch: master Commit: e7425d218f38 Files: 59 Total size: 139.7 KB Directory structure: gitextract_tw1fz8qn/ ├── .circleci/ │ └── config.yml ├── .github/ │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ └── feature_request.md │ ├── IssueTemplate/ │ │ ├── BugReportTemplate.md │ │ └── FeatureRequestTemplate.md │ ├── Pull_Request_Template.md │ └── workflows/ │ ├── pylint.yml │ └── python-app.yml ├── .gitignore ├── .gitpod/ │ ├── .gitpod.Dockerfile │ └── .gitpod.yml ├── .travis.yml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── README.md ├── builds/ │ ├── build.bat │ └── build.sh ├── requirements.txt ├── setup.py ├── tests/ │ ├── test_func.py │ ├── test_helpers.py │ ├── test_kill_child_process.py │ └── test_logger.py └── wplay/ ├── __init__.py ├── __main__.py ├── about_changer.py ├── broadcast_message.py ├── chat_intermediator.py ├── chatbot.py ├── download_media.py ├── get_media.py ├── get_news.py ├── message_blast.py ├── message_service.py ├── message_timer.py ├── online_tracker.py ├── profile_download.py ├── save_chat.py ├── schedule_message.py ├── settings.cfg ├── target_info.py ├── telegram_bot.py ├── terminal_chat.py ├── text_to_speech.py └── utils/ ├── Logger.py ├── MessageStack.py ├── SessionManager.py ├── TODO ├── __init__.py ├── browser_config.py ├── helpers.py ├── io.py ├── target_data.py ├── target_search.py ├── target_select.py └── verify_internet.py ================================================ FILE CONTENTS ================================================ ================================================ FILE: .circleci/config.yml ================================================ # Python CircleCI 2.0 configuration file # # Check https://circleci.com/docs/2.0/language-python/ for more details # version: 2 jobs: build: docker: - image: circleci/python:3.7-node-browsers-legacy steps: - checkout # Download and cache dependencies - restore_cache: keys: - v1-dependencies-{{ checksum "requirements.txt" }} # fallback to using the latest cache if no exact match is found - v1-dependencies- - run: name: install dependencies command: | python3 -m venv venv . venv/bin/activate pip install --upgrade pip pip install -r requirements.txt - save_cache: paths: - ./venv key: v1-dependencies-{{ checksum "requirements.txt" }} - run: name: run tests command: | . venv/bin/activate python3 -m unittest discover -s tests # name: run linting and metrics # command: | # . venv/bin/activate # flake8 . --count --exit-zero --max-complexity=10 --max-line-length=500 --statistics --ignore=C901,E251,E722,E231,E902 --exclude=.git,.venv,.gitignore - store_artifacts: path: test-reports destination: test-reports ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms # repo: rpotter12/whatsapp-play # filename: FUNDING.YML github: [rpotter12] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] patreon: # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry liberapay: # Replace with a single Liberapay username issuehunt: # Replace with a single IssueHunt username otechie: # Replace with a single Otechie username custom: ["https://paypal.me/rpotter12"] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Create a report to help us improve title: '' labels: '' assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error **Expected behavior** A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. **Desktop (please complete the following information):** - OS: [e.g. iOS] - Browser [e.g. chrome, safari] - Version [e.g. 22] **Smartphone (please complete the following information):** - Device: [e.g. iPhone6] - OS: [e.g. iOS8.1] - Browser [e.g. stock browser, safari] - Version [e.g. 22] **Additional context** Add any other context about the problem here. ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature request about: Suggest an idea for this project title: '' labels: '' assignees: '' --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. ================================================ FILE: .github/IssueTemplate/BugReportTemplate.md ================================================ --- name: Bug report about: Create a report to help us improve title: '' labels: '' assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: 1. Go to 2. Click on 3. Scroll down to 4. See error **Expected behavior** A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. **Desktop (please complete the following information):** - OS: - Browser - Version **Additional context** Add any other context about the problem here. ================================================ FILE: .github/IssueTemplate/FeatureRequestTemplate.md ================================================ --- name: Feature request about: Suggest an idea for this project title: '' labels: '' assignees: '' --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. ================================================ FILE: .github/Pull_Request_Template.md ================================================ ## Issue that this pull request solves Closes: # (issue number) ## Proposed changes Brief description of what is fixed or changed ## Types of changes _Put an `x` in the boxes that apply_ - [ ] Bugfix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) - [ ] Documentation update (Documentation content changed) - [ ] Other (please describe): ## Checklist _Put an `x` in the boxes that apply_ - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings ## Screenshots Please attach the screenshots of the changes made in case of change in user interface ## Other information Any other information that is important to this pull request ================================================ FILE: .github/workflows/pylint.yml ================================================ name: Pylint on: [push] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Set up Python 3.8 uses: actions/setup-python@v1 with: python-version: 3.8 - name: Install dependencies run: | python -m pip install --upgrade pip pip install pylint - name: Analysing the code with pylint run: | # pylint `ls -R|grep .py$|xargs` pylint wplay/about_changer.py pylint wplay/broadcast_message.py pylint wplay/chat_intermediator.py pylint wplay/chatbot.py pylint wplay/download_media.py pylint wplay/get_media.py pylint wplay/get_news.py pylint wplay/message_blast.py pylint wplay/message_service.py pylint wplay/message_timer.py pylint wplay/online_tracker.py pylint wplay/profile_download.py pylint wplay/save_chat.py pylint wplay/schedule_message.py pylint wplay/target_info.py pylint wplay/telegram_bot.py pylint wplay/terminal_chat.py pylint wplay/text_to_speech.py ================================================ FILE: .github/workflows/python-app.yml ================================================ # This workflow will install Python dependencies, run tests and lint with a single version of Python # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions name: Python application on: push: branches: [ master ] pull_request: branches: [ master ] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Set up Python 3.9 uses: actions/setup-python@v2 with: python-version: 3.9 - name: Install dependencies run: | python -m pip install --upgrade pip pip install flake8 pytest pip install wplay if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - name: Lint with flake8 run: | # stop the build if there are Python syntax errors or undefined names # flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - name: Test with pytest run: | pytest ================================================ FILE: .gitignore ================================================ # Personal Data .userData tracking_data data telegram_token.pkl # Byte-compiled / optimized / DLL files wplay.egg-info/ __pycache__/ *.pyc __pycache__/ src/\.DS_Store src/__pycache__/ src/logs/ src/test/__pycache__/ \.DS_Store \.idea/ src/XDG_CACHE_HOME/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python ImplementationTests build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ pip-wheel-metadata/ share/python-wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover .hypothesis/ .pytest_cache/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 db.sqlite3-journal # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # Jupyter Notebook .ipynb_checkpoints # IPython profile_default/ ipython_config.py # pyenv .python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. # However, in case of collaboration, if having platform-specific dependencies or dependencies # having no cross-platform support, pipenv may install dependencies that don't work, or not # install all needed dependencies. #Pipfile.lock # celery beat schedule file celerybeat-schedule # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ .dmypy.json dmypy.json # Pyre type checker .pyre/ # Visual Studio .vs # Visual Studio Code .vscode # React # dependencies /node_modules /.pnp .pnp.js # testing /coverage # production /build # misc .DS_Store .env.local .env.development.local .env.test.local .env.production.local npm-debug.log* yarn-debug.log* yarn-error.log* ================================================ FILE: .gitpod/.gitpod.Dockerfile ================================================ FROM custom USER gitpod # Install custom tools, runtime, etc. using apt-get # For example, the command below would install "bastet" - a command line tetris clone: # # RUN sudo apt-get -q update && # sudo apt-get install -yq bastet && # sudo rm -rf /var/lib/apt/lists/* # # More information: https://www.gitpod.io/docs/config-docker/ ================================================ FILE: .gitpod/.gitpod.yml ================================================ tasks: - init: pip install -r ./requirements.txt image: file: .gitpod.Dockerfile ================================================ FILE: .travis.yml ================================================ language: python #sudo: false python: - 3.6 - 3.7 - 3.8 - pypy3 install: - python -m pip install -U pip - pip install -r requirements.txt - pip install unittest2 - python setup.py install - pip install coverage script: - python3 -m unittest discover -s tests - coverage run -m unittest discover -s tests after_success: # - codecov - bash <(curl -s https://codecov.io/bash) ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at rohitpotter12@gmail.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing guidelines To start contributing to [whatsapp-play](https://github.com/rpotter12/whatsapp-play) project, please first discuss the change you wish to make via creating an issue in the issue section or any other method with the owners of this repository before making a change.
We have a code of conduct, please follow it in all your interactions with the project.
# Contributing to whatsapp-play :+1::tada: First off, thanks for taking the time to contribute! :tada::+1: We love your input! We want to make contributing to this project as easy and transparent as possible, whether it's: - Discussing the current state of the code - [Reporting a bug]( https://github.com/rpotter12/whatsapp-play/blob/master/.github/IssueTemplate/BugReportTemplate.md) - [Submitting a fix](https://github.com/rpotter12/whatsapp-play/blob/master/.github/Pull_Request_Template.md) - [Proposing new features]( https://github.com/rpotter12/whatsapp-play/blob/master/.github/IssueTemplate/FeatureRequestTemplate.md) # Points to remember 1.Ensure any install or build dependencies are checked and verified before the end when releasing a build.
2.Update the README.md with details of changes including environment variables, various file parameters and container details **if needed**.
3.Try adding screenshots in your pull request of the changes you made.
4.You may be able to merge the pull request in once you have the sign-off of two other developers, or if you do not have permission to do that, you may request the maintainer or reviewer to merge it for you. # Steps to follow :scroll: ### 1. Fork it :fork_and_knife: You can get your own fork/copy of [whatsapp-play]( https://github.com/rpotter12/whatsapp-play) by using the Fork button. [![Fork Button](https://help.github.com/assets/images/help/repository/fork_button.jpg)](https://github.com/rpotter12/whatsapp-play) ### 2. Clone it :busts_in_silhouette: You need to clone (download) it to local machine using ```sh git clone https://github.com/Your_Username/whatsapp-play.git ``` > This makes a local copy of repository in your machine. Once you have cloned the ` whatsapp-play ` repository in GitHub, move to that folder first using change directory command. ```sh # This will change directory to a folder whatsapp-play cd whatsapp-play ``` Move to this folder for all other commands. ### 3. Set it up :arrow_up: Run the following commands to see that *your local copy* has a reference to *your forked remote repository* in GitHub :octocat: ```sh git remote -v origin https://github.com/Your_Username/whatsapp-play.git (fetch) origin https://github.com/Your_Username/whatsapp-play.git (push) ``` Now, add a reference to the original [whatsapp-play](https://github.com/rpotter12/whatsapp-play) repository using ```sh git remote add upstream https://github.com/rpotter12/whatsapp-play.git ``` > This adds a new remote named ***upstream***. See the changes using ```sh git remote -v origin https://github.com/Your_Username/whatsapp-play.git (fetch) origin https://github.com/Your_Username/whatsapp-play.git (push) upstream https://github.com/rpotter12/whatsapp-play.git (fetch) upstream https://github.com/rpotter12/whatsapp-play.git (push) ``` ### 4. Sync it :recycle: Always keep your local copy of repository updated with the original repository. Before making any changes and/or in an appropriate interval, run the following commands *carefully* to update your local repository. ```sh # Fetch all remote repositories and delete any deleted remote branches git fetch --all --prune # Switch to `master` branch git checkout master # Reset local `master` branch to match `upstream` repository's `master` branch git reset --hard upstream/master # Push changes to your forked `whatsapp-play` repo git push origin master ``` ### 5. Ready Steady Go :turtle: :rabbit2: Once you have completed these steps, you are ready to start contributing by checking our `Help Wanted` Issues and creating [pull requests](https://github.com/rpotter12/whatsapp-play/pulls). ### 6. Create a new branch :bangbang: Whenever you are going to make contribution. Please create separate branch using command and keep your `master` branch clean (i.e. synced with remote branch). ```sh # It will create a new branch with name Branch_Name and will switch to that branch. git checkout -b Branch_Name ``` Create a separate branch for contribution and try to use same name of branch as of folder. To switch to desired branch ```sh # To switch from one folder to other git checkout Branch_Name ``` To add the changes to the branch. Use ```sh # To add all files to branch Branch_Name git add . ``` Type in a message relevant for the code reviewer using ```sh # This message gets associated with all files you have changed git commit -m 'relevant message' ``` Now, Push your awesome work to your remote repository using ```sh # To push your work to your remote repository git push -u origin Branch_Name ``` Finally, go to your repository in browser and click on `compare and pull requests`. Use our [pull request template format]( https://github.com/rpotter12/whatsapp-play/blob/master/.github/Pull_Request_Template.md) Then add a title and description to your pull request that explains your precious effort. Sit and relax till we review your PR, you've made your contribution to our project. :tada: :confetti_ball: :smiley: _**Happy Contributing**_ :smiley: :confetti_ball: :tada: ================================================ FILE: Dockerfile ================================================ #To build docker image from this file run #docker build . #on terminal FROM python:3.6-alpine #LABEL MAINTAINER # Copying files COPY wplay/ /whatsapp-play/wplay COPY setup.py /whatsapp-play/setup.py COPY README.md /whatsapp-play/README.md COPY requirements.txt /whatsapp-play/requirements.txt # Dependencies WORKDIR /whatsapp-play RUN apk add build-base RUN apk add make RUN apk add gcc musl-dev libffi-dev openssl-dev RUN pip install cryptography==2.9.0 RUN apk add --no-cache libffi-dev RUN apk add build-base RUN apk add py3-pip RUN apk add python3-dev RUN pip install cffi==1.14.0 RUN pip install -r requirements.txt #ENTRYPOINT echo "Hello, welcome to whatsapp-play" ENTRYPOINT ["python3 -m wplay -h"] CMD [ "python"] ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2019 Rohit Potter 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. ================================================ FILE: README.md ================================================
# whatsapp-play [![Downloads](https://pepy.tech/badge/wplay)](https://pepy.tech/project/wplay) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/749acf4cad424fbeb96a412963aa83ea)](https://app.codacy.com/app/rpotter12/whatsapp-play?utm_source=github.com&utm_medium=referral&utm_content=rpotter12/whatsapp-play&utm_campaign=Badge_Grade_Settings) [![PyPi](https://img.shields.io/pypi/v/wplay)](https://pypi.org/project/wplay/) ![CircleCI](https://circleci.com/gh/rpotter12/whatsapp-play/tree/master.svg?style=svg&circle-token=2b67dd21e60a01fdd36a670629574479aeb2f5c4) [![Docker](https://img.shields.io/docker/cloud/build/rpotter12/whatsapp-play)](https://hub.docker.com/repository/docker/rpotter12/whatsapp-play/general) [![Build Status](https://travis-ci.org/rpotter12/whatsapp-play.svg?branch=master)](https://travis-ci.org/rpotter12/whatsapp-play) [![codecov](https://codecov.io/gh/rpotter12/whatsapp-play/branch/master/graph/badge.svg)](https://codecov.io/gh/rpotter12/whatsapp-play) [![twitter](https://img.shields.io/twitter/url/https/github.com/rpotter12/whatsapp-play.svg?style=social)](https://twitter.com/rpotter121998) [![HitCount](http://hits.dwyl.io/rpotter12/whatsapp-play.svg)](http://hits.dwyl.io/rpotter12/whatsapp-play) [![Gitter](https://badges.gitter.im/whatsapp-play/community.svg)](https://gitter.im/whatsapp-play/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/rpotter12/whatsapp-play)
A command line software through which you can play with your WhatsApp. The software aims to provide all the facilities to use and implement a multitude of WhatsApp features. Blog link: [https://github.com/rpotter12/rpotter12.github.io/blob/master/blogs/blog3-tracking-26-07-2019.md](https://github.com/rpotter12/rpotter12.github.io/blob/master/blogs/blog3-tracking-26-07-2019.md) ## Features - Online/offline Tracker - Terminal Chat - Chat Intermediator - Message Service - Telegram Bot - Message Blast - Message Timer - Save Chat - Schedule Message - About Changer - Get News (Get API from and paste it in newsapi = NewsApiClient(api_key="YOUR API KEY")) - Get Profile Photos - Broadcast Message - Target Info (Profile Info) - Download Media --- ## Installation ### Install whatsapp-play from PyPI:
Windows: `python -m pip install wplay`
Unix/Linux/Mac: `python3 -m pip install wplay`
**Installation Video:** [Simple Installation Link](https://youtu.be/HS6ksu6rCxQ) ### Alternate way - Run whatsapp-play from source code:
**Windows**
``` $ git clone https://github.com/rpotter12/whatsapp-play.git $ cd whatsapp-play $ python -m pip install -r requirements.txt $ python -m wplay -h ``` **Unix/Linux/Mac**
``` $ git clone https://github.com/rpotter12/whatsapp-play.git $ cd whatsapp-play $ python3 -m pip install -r requirements.txt $ python3 -m wplay -h ``` ## Usage
For detailed usage of command visit: [https://github.com/rpotter12/whatsapp-play/wiki/Usage](https://github.com/rpotter12/whatsapp-play/wiki/Usage) ## Contribute The easiest way to contribute to **Whatsapp-Play** is by starring the repository and opening more and more [issues](https://github.com/rpotter12/whatsapp-play/issues) for features you'd like to see in future.
First step is to create a fork and clone, then you can solve the [issues](https://github.com/rpotter12/whatsapp-play/issues) listed and help us find new ones. Then try debugging with Visual Studio Code it is necessary to create a launcher with the arguments.
Steps to create a launcher with arguments follow the steps bellow:
1. Click in 'Debug' tab 1. Click in 'Add Configuration' 1. Select 'Module' 1. Type 'wplay' and press Enter 1. A json file will be opened. Inside configurations add the args, for example: "args":["-wb","name"] **Debug Tutorial Video:** [Debug Tutorial Link](https://youtu.be/NyJgUGvyWnY)
Check more about contribution guidelines [here](https://www.github.com/rpotter12/whatsapp-play/CONTRIBUTION.md) ## Disclaimer This software is for educational purpose only. Keeping eye on a innocent person can make person's life stressful. ## License [![License](https://img.shields.io/github/license/rpotter12/whatsapp-play.svg)](https://github.com/rpotter12/whatsapp-play/blob/master/README.md) ***If you like the project, support us by star*** ================================================ FILE: builds/build.bat ================================================ python setup.py sdist bdist_wheel ================================================ FILE: builds/build.sh ================================================ #!/usr/bin/env bash python3 setup.py sdist bdist_wheel ================================================ FILE: requirements.txt ================================================ argparse>=1.4.0 beautifulsoup4>=4.8.1 colorama>=0.4.3 DateTime>=4.3 decorator>=4.4.2 flake8>=3.7.9 google>=2.0.3 gTTS==2.1.1 newsapi-python>=0.2.6 phonenumbers==8.10.2 playsound>=1.2.2 prompt_toolkit==1.0.14 psutil>=5.7.0 pycodestyle>=2.6.0 pycparser>=2.20 pyee>=7.0.2 pyfiglet>=0.8.post1 pyflakes>=2.2.0 Pygments>=2.6.1 pyppeteer>=0.2.2 python-dotenv>=0.12.0 python-telegram-bot>=12.7 requests>=2.22.0 transitions>=0.7.2 urllib3>=1.25.8 websockets>=8.1 whaaaaat>=0.5.2 ================================================ FILE: setup.py ================================================ from setuptools import setup, find_packages with open("README.md", "r") as f: long_description = f.read() setup( name="wplay", version="8.0.5", install_requires=["argparse >= 1.4.0", "beautifulsoup4>=4.8.1", "colorama>=0.4.3", "dateTime>=4.3", "decorator>=4.4.2", "flake8>=3.7.9", "google>=2.0.3", "gTTS==2.1.1", "newsapi-python>=0.2.6", "phonenumbers==8.10.2", "playsound>=1.2.2", "prompt_toolkit==1.0.14", "psutil>=5.7.0", "pyfiglet>=0.8.post1", "pyflakes>=2.2.0", "Pygments>=2.6.1", "pyppeteer>=0.0.25", "python-dotenv==0.12.0", "python-telegram-bot>=11.1.0", "requests>=2.22.0", "transitions>=0.7.2", "urllib3>=1.25.8", "websockets>=8.1", "whaaaaat>=0.5.2", ], packages=find_packages(), description="command line software to play with your WhatsApp", long_description=long_description, long_description_content_type="text/markdown", author="Rohit Potter, Alexandre Calil", author_email="rohitpotter12@gmail.com, alexandrecalilmf@gmail.com", license="MIT", python_requires=">=3.6", url="https://github.com/rpotter12/whatsapp-play/", download_url="https://pypi.org/project/wplay/", keywords=[ "whatsapp", "whatsapp cli", "whatsapp api", "whatsapp service" "whatsapp chat", "message blast", "message timer", "whatsapp terminal", "whatsapp news", "whatsapp schedule", "tracker", "online tracking", "save-chat" ], classifiers=[ "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3 :: Only", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", ], entry_points={"console_scripts": ["wplay = wplay.__main__:main"]}, ) ================================================ FILE: tests/test_func.py ================================================ #region imports import functools import os import shutil import stat import subprocess import sys import time import psutil from psutil import MACOS from psutil import POSIX from psutil import WINDOWS from psutil._compat import which #endregion __all__ = [ # constants 'DEVNULL' , 'PYTHON_EXE', 'TESTFILE_PREFIX' , 'TESTFN', # subprocesses 'get_test_subprocess', # fs utils 'safe_rmpath' , # sync primitives 'wait_for_pid', 'wait_for_file', ] TESTFILE_PREFIX = '$testfn' if os.name == 'java': # Jython disallows @ in module names TESTFILE_PREFIX = '$psutil-test-' else: TESTFILE_PREFIX = '@psutil-test-' TESTFN = os.path.join(os.path.realpath(os.getcwd()), TESTFILE_PREFIX) TESTFN = TESTFN + str(os.getpid()) _TESTFN = TESTFN + '-internal' def _get_py_exe(): def attempt(exe): try: subprocess.check_call( [exe, "-V"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) except Exception: return None else: return exe if MACOS: exe = \ attempt(sys.executable) or \ attempt(os.path.realpath(sys.executable)) or \ attempt(which("python%s.%s" % sys.version_info[:2])) or \ attempt(psutil.Process().exe()) if not exe: raise ValueError("can't find python exe real abspath") return exe else: exe = os.path.realpath(sys.executable) assert os.path.exists(exe), exe return exe PYTHON_EXE = _get_py_exe() DEVNULL = open(os.devnull, 'r+') _subprocesses_started = set() def get_test_subprocess(cmd=None, **kwds): """Creates a python subprocess which does nothing for 30 secs and return it as subprocess.Popen instance. If "cmd" is specified that is used instead of python. By default stdin and stdout are redirected to /dev/null. It also attemps to make sure the process is in a reasonably initialized state. The process is registered for cleanup on reap_children(). """ kwds.setdefault("stdin", DEVNULL) kwds.setdefault("stdout", DEVNULL) kwds.setdefault("cwd", os.getcwd()) kwds.setdefault("env", os.environ) if WINDOWS: # Prevents the subprocess to open error dialogs. This will also # cause stderr to be suppressed, which is suboptimal in order # to debug broken tests. CREATE_NO_WINDOW = 0x8000000 kwds.setdefault("creationflags", CREATE_NO_WINDOW) if cmd is None: safe_rmpath(_TESTFN) pyline = "from time import sleep;" \ "open(r'%s', 'w').close();" \ "sleep(30);" % _TESTFN cmd = [PYTHON_EXE, "-c", pyline] sproc = subprocess.Popen(cmd, **kwds) _subprocesses_started.add(sproc) #wait_for_file(_TESTFN, delete=True, empty=True) else: sproc = subprocess.Popen(cmd, **kwds) _subprocesses_started.add(sproc) wait_for_pid(sproc.pid) return sproc def wait_for_pid(pid): """Wait for pid to show up in the process list then return. Used in the test suite to give time the sub process to initialize. """ psutil.Process(pid) if WINDOWS: # give it some more time to allow better initialization time.sleep(0.01) def wait_for_file(fname, delete=True, empty=False): """Wait for a file to be written on disk with some content.""" with open(fname, "rb") as f: data = f.read() if not empty: assert data if delete: safe_rmpath(fname) return data def safe_rmpath(path): "Convenience function for removing temporary test files or dirs" def retry_fun(fun): # On Windows it could happen that the file or directory has # open handles or references preventing the delete operation # to succeed immediately, so we retry for a while. See: # https://bugs.python.org/issue33240 stop_at = time.time() + 1 while time.time() < stop_at: try: return fun() except FileNotFoundError: pass try: st = os.stat(path) if stat.S_ISDIR(st.st_mode): fun = functools.partial(shutil.rmtree, path) else: fun = functools.partial(os.remove, path) if POSIX: fun() else: retry_fun(fun) except FileNotFoundError: pass ================================================ FILE: tests/test_helpers.py ================================================ ''' To run tests run python3 -m unittest discover -s tests on terminal ''' #region imports import unittest from os import path from wplay.utils import helpers #endregion #region class class testPath(unittest.TestCase): def test_paths_exist(self): # self.assertTrue(path.exists(helpers.audio_file_folder_path)) # self.assertTrue(path.exists(helpers.chatbot_image_folder_path)) self.assertTrue(path.exists(helpers.data_folder_path)) self.assertTrue(path.exists(helpers.logs_path)) self.assertTrue(path.exists(helpers.log_file_path)) # self.assertTrue(path.exists(helpers.media_path)) # self.assertTrue(path.exists(helpers.messages_json_folder_path)) # self.assertTrue(path.exists(helpers.messages_json_path)) # self.assertTrue(path.exists(helpers.open_messages_json_path)) # self.assertTrue(path.exists(helpers.profile_photos_path)) # self.assertTrue(path.exists(helpers.save_chat_folder_path)) self.assertTrue(path.exists(helpers.test_log_file_path)) # self.assertTrue(path.exists(helpers.tracking_folder_path)) # self.assertTrue(path.exists(helpers.user_data_folder_path)) if __name__ == '__main__': unittest.main() #endregion ================================================ FILE: tests/test_kill_child_process.py ================================================ ''' To run tests run python3 -m unittest discover -s tests on terminal ''' #region imports import unittest import psutil import test_func from wplay.utils.helpers import kill_child_processes #endregion #region class for test_kill_child_processes function class Testkill(unittest.TestCase): def test_kill(self): sproc = test_func.get_test_subprocess() test_pid = sproc.pid p = psutil.Process(test_pid) kill_child_processes(test_pid) p.wait() self.assertFalse(psutil.pid_exists(test_pid)) if __name__ == '__main__': unittest.main() #endregion ================================================ FILE: tests/test_logger.py ================================================ ''' To run tests run python3 -m unittest discover -s tests on terminal ''' #region imports import unittest from wplay.utils.Logger import Logger from pathlib import Path #endregion #region LOGGER create logger = Logger(Path(__file__).name) #endregion #region class for Logger function class CaptureLogsExample(unittest.TestCase): def test_assert_logs(self): """Verify logs using built-in self.assertLogs().""" logger.error("Testing logg class") self.assertTrue(logger, 'test_logger.py - ERROR - Testing logg class') if __name__ == '__main__': unittest.main() #endregion ================================================ FILE: wplay/__init__.py ================================================ ================================================ FILE: wplay/__main__.py ================================================ # region IMPORTS import argparse import asyncio import sys import os from pathlib import Path from pyfiglet import Figlet from wplay import online_tracker from wplay import message_blast from wplay import message_timer from wplay import terminal_chat from wplay import chat_intermediator from wplay import broadcast_message from wplay import save_chat from wplay import telegram_bot from wplay import schedule_message from wplay import about_changer from wplay import get_news from wplay import get_media from wplay import download_media from wplay import message_service from wplay import target_info from wplay import profile_download from wplay.utils.Logger import Logger from wplay.utils.helpers import create_dirs from wplay.utils.helpers import kill_child_processes # endregion def print_logo(text_logo): figlet = Figlet(font='slant') print(figlet.renderText(text_logo)) # parse positional and optional arguments def get_arg_parser(): parser = argparse.ArgumentParser(description='WhatsApp-play') parser.add_argument( "target", metavar="TARGET", type=str, default=None, nargs="?", help="""contact or group name, optional, target can be selected manually except for saving chat""") parser.add_argument('-s', '--sender', help='contact or group name') parser.add_argument('-r', '--receiver', help='contact or group name') group = parser.add_mutually_exclusive_group(required=True) group.add_argument( "-wc", "--terminal-chat", action="store_true", help="chatting from command line") group.add_argument( "-wi", "--chat-intermediator", action="store_true", help='Be an Intermediator from command line -wi -s -r ') group.add_argument( "-wpd", "--profile-download", action="store_true", help='Download the peofile picture of target') group.add_argument( "-wms", "--message-service", action="store_true", help="send messages from a JSON file") group.add_argument( "-wb", "--message-blast", action="store_true", help="message blast to a person") group.add_argument( "-wti", "--message-timer", action="store_true", help="send messages from time to time") group.add_argument( "-wt", "--online-tracker", action="store_true", help="track online status of person") group.add_argument( "-wtb", "--telegram-bot", action="store_true", help="sends tracking status to telegram bot") group.add_argument( "-wsc", "--save-chat", action="store_true", help="save all chats from Google Drive, target is necessary") group.add_argument( "-ws", "--schedule-message", action="store_true", help="send the message at scheduled time") group.add_argument( "-wa", "--about-changer", action="store_true", help="Changes the about section") group.add_argument( "-wgn", "--get-news", action="store_true", help="Get news in whatsapp group") group.add_argument( "-wgp", "--get-profile-photos", action="store_true", help="Get profile photo of all your contacts") group.add_argument( "-wbc", "--broadcast", action="store_true", help="Broadcast message") group.add_argument( "-wtf", "--target-info", action="store_true", help="finds the information about target's contact") group.add_argument( "-wd", "--download-media", action="store_true", help="Download the media of the target's contact") return parser # functions for different arguments async def get_and_match_args(parser): args = parser.parse_args() if args.online_tracker: await online_tracker.tracker(args.target) elif args.message_service: await message_service.message_service() elif args.telegram_bot: telegram_bot.telegram_status(args.target) elif args.terminal_chat: await terminal_chat.chat(args.target) elif args.chat_intermediator: await chat_intermediator.intermediary(args.sender, args.receiver) elif args.profile_download: await profile_download.get_profile_picture(args.target) elif args.broadcast: await broadcast_message.broadcast() elif args.message_blast: await message_blast.message_blast(args.target) elif args.message_timer: await message_timer.message_timer(args.target) elif args.schedule_message: await schedule_message.schedule_message(args.target) elif args.about_changer: await about_changer.about_changer() elif args.get_news: await get_news.get_news(args.target) elif args.get_profile_photos: await get_media.get_profile_photos() elif args.target_info: await target_info.target_info(args.target) elif args.download_media: await download_media.download_media(args.target) elif args.save_chat: await save_chat.save_chat(args.target) async def main(): print_logo("wplay") create_dirs() parser = get_arg_parser() try: await get_and_match_args(parser) sys.exit(0) except KeyboardInterrupt: sys.exit(0) try: asyncio.get_event_loop().run_until_complete(main()) except KeyboardInterrupt: pass except AssertionError: try: for task in asyncio.all_tasks(): task.cancel() except RuntimeError: exit() exit() finally: kill_child_processes(os.getpid()) ================================================ FILE: wplay/about_changer.py ================================================ # region IMPORTS import time from pathlib import Path from wplay.utils.helpers import whatsapp_selectors_dict from wplay.utils import browser_config from wplay.utils.Logger import Logger from newsapi.newsapi_client import NewsApiClient # endregion # region LOGGER __logger = Logger(Path(__file__).name) # endregion # Asking user async def about_changer(): option = input("Choose(1/2) \n1.Write new about \n2.Change about with latest headline\n") if option == '1': await change_about() else: await about_changer_news() # Custom About async def change_about(): page, _ = await browser_config.configure_browser_and_load_whatsapp() # opens photo element await page.waitForSelector(whatsapp_selectors_dict['profile_photo_element'], visible=True) await page.click(whatsapp_selectors_dict['profile_photo_element']) await page.waitForSelector(whatsapp_selectors_dict['about_edit_button_element']) await page.click(whatsapp_selectors_dict['about_edit_button_element']) status = input("Enter your new about: ") __logger.info("Writing About") # Write about await page.type(whatsapp_selectors_dict['about_text_area'], status) await page.keyboard.press('Enter') print("About changed to {}".format(status)) async def get_api_key(): __logger.info("Getting key") print("Visit https://newsapi.org/ to get your own API key") key = input("Enter you API KEY : ") get_api_key.newsapi = NewsApiClient(api_key='{}'.format(key)) # News in About async def about_changer_news(): page, _ = await browser_config.configure_browser_and_load_whatsapp() await get_api_key() query: str = str(input("What's the news theme? : ")) # opens photo element await page.waitForSelector(whatsapp_selectors_dict['profile_photo_element'], visible=True) await page.click(whatsapp_selectors_dict['profile_photo_element']) news = '' while True: current_news = str(fetch_news(query)) print(current_news) if news != current_news: # Click on edit about button await page.waitForSelector(whatsapp_selectors_dict['about_edit_button_element']) await page.click(whatsapp_selectors_dict['about_edit_button_element']) for _ in range(140): await page.keyboard.press('Backspace') news = current_news __logger.info("Updating About latest news") # Write about await page.type(whatsapp_selectors_dict['about_text_area'], news) await page.keyboard.press('Enter') # News get updated every 15 minutes by newsapi.org # Added extra minute for buffer period # For free account : max limit is 500 request/day time.sleep(905) def fetch_news(query): __logger.info("Fetching news") top_headlines = get_api_key.newsapi.get_top_headlines(q=query, language='en') return top_headlines['articles'][0]['title'] ================================================ FILE: wplay/broadcast_message.py ================================================ # region IMPORTS from tkinter import Tk from tkinter.filedialog import askopenfile from pathlib import Path from wplay.utils import browser_config from wplay.utils.target_search import search_target_by_number from wplay.utils import io from typing import List from wplay.utils.helpers import data_folder_path from wplay.utils.Logger import Logger # endregion # region LOGGER __logger = Logger(Path(__file__).name) # endregion class InvalidNumber(Exception): message = "Either Number is invalid or no account exist for the number or the number was kept in wrong format :(\n" def ProcessNumbers(): __logger.info("Processing numbers.") print("Choose a text file containing full numbers with country code, one number per line.") Tk().withdraw() filename = askopenfile( initialdir=data_folder_path, title='Choose a text file with numbers.', filetypes=[("text files", "*.txt")], mode="r" ) numbers = filename.readlines() for i in range(len(numbers)): number = numbers[i].strip("\n+") numbers[i] = number return numbers async def broadcast(): __logger.info("Broadcast message.") page, _ = await browser_config.configure_browser_and_load_whatsapp() numbers = ProcessNumbers() message: List[str] = io.ask_user_for_message_breakline_mode() for number in numbers: if await search_target_by_number(page, number): await io.send_message(page, message) __logger.info("Messages broadcasted successfully!") print("Messages broadcasted successfully!") ================================================ FILE: wplay/chat_intermediator.py ================================================ # region IMPORTS from wplay import terminal_chat from wplay.utils.Logger import Logger from pathlib import Path # endregion # region LOGGER __logger = Logger(Path(__file__).name) # endregion async def intermediary(sender, receiver): """ Function to create intermediate between two person. """ __logger.info("Being and Intermediator") intermediary.rec = receiver await terminal_chat.chat(sender) ================================================ FILE: wplay/chatbot.py ================================================ from googlesearch import search from pyppeteer import launch from wplay.utils.helpers import chatbot_image_folder_path async def Bot(last_Message): """ Function to perform instruction as instructed to bot. """ print('\n Bot activated') first_last_Message = "".join(last_Message.split()) simple_menu = { "hi": say_hi, "help": _help_commands, "goodmorning": say_goodmorning, "goodnight": say_goodnight, "howareyou?": say_fine, } simple_menu_keys = simple_menu.keys() result = [] try: command_args = first_last_Message[1:].split(" ", 1) command_arg = last_Message[1:].split(" ", 1) if len(command_args) == 1 and command_args[0] in simple_menu_keys: return simple_menu[command_args[0]]() elif command_arg[0] == 'google': query = "".join(command_arg[1]) for j in search(query, tld="co.in", num=10, stop=10, pause=2): result.append(j) print("Sending links for query") return result elif command_arg[0] == "image": query = "".join(command_arg[1]) await takeScreenshot(query) print("Taking screenshot of google image for query") return "Sending you screenshot" elif command_arg[0] == "maps": query = "".join(command_arg[1]) map_parameters_list = query.replace(" ", "") map_parameters = map_parameters_list.split(',') base_url = "https://www.google.com/maps/dir/?api=1&" custom_url = base_url + "origin={ori}&destination={dest}&travelmode={t_mode}".format(ori=map_parameters[0], dest=map_parameters[1], t_mode=map_parameters[2]) print("Sending link for google maps") return custom_url else: return "Wrong command. Send me /help to see a list of valid commands" except KeyError as e: print("Key Error Exception: {err}".format(err=str(e))) def say_hi(): print("Saying hi") return "Wplay chatbot says hi! Hope you are having a nice day..." def say_goodmorning(): print("Saying good morning") return "Bot says Good Morning! Have a Good Day..." def say_goodnight(): print("Saying good night") return "Bot says Good Night! Sweet Dreams..." def say_fine(): print("Saying I am Fine!") return "Bot says I am Fine Thank You! How are you?" def _help_commands(): print("Asking for help") return "How may I assist you with help\n"\ "List of commands:\n" \ "/hi (bot says hi), " \ "/all_commands (ist of all commands), " \ "/good morning, " \ "/good night, " \ "/how are you? " \ "/google {query} " \ "/image {query} " \ "/maps {origin}, {destination}, {mode:driving/bicycling/transit/two-wheeler/walking}" async def takeScreenshot(qry): browser = await launch() page = await browser.newPage() await page.goto('https://www.google.com/search?q={}&source=lnms&tbm=isch'.format(qry)) image_path = str(chatbot_image_folder_path / '{}.png'.format(qry)) await page.screenshot({'path': image_path}) await browser.close() ================================================ FILE: wplay/download_media.py ================================================ # region Imports from pathlib import Path from wplay.utils import browser_config from wplay.utils import target_search from wplay.utils import target_select from wplay.utils.helpers import media_path from wplay.utils.Logger import Logger from wplay.utils.helpers import whatsapp_selectors_dict import time # endregion # region LOGGER __logger = Logger(Path(__file__).name) # endregion async def download_media(target): page, browser = await browser_config.configure_browser_and_load_whatsapp() if target is not None: try: await target_search.search_and_select_target(page, target) except Exception as e: print(e) await page.reload() await target_search.search_and_select_target_without_new_chat_button( page, target ) else: await target_select.manual_select_target(page) count = int(input("Count of media you want to download: ")) # Click on the photo element of the target await page.waitForSelector(whatsapp_selectors_dict['target_name_selector'], visible=True) await page.evaluate(f'''document.querySelector('{whatsapp_selectors_dict['target_name_selector']}').click()''') time.sleep(1) # Click on the `Media, Link and Docs` text await page.waitForSelector(whatsapp_selectors_dict['media_text']) await page.click(whatsapp_selectors_dict['media_text']) # Click on the most recent media element while True: try: await page.evaluate(f'''document.querySelector('{whatsapp_selectors_dict['media_images']}').click()''') break except Exception as e: print("", end='') media_arr = {'img': [], 'vid': []} # Currently downloads the last 50 medias for _ in range(count): try: try: # If media is an image countTry = 0 # Threshold of how many times to try looking for media while True: if countTry > 500: await page.waitForSelector(whatsapp_selectors_dict['left_arrow_button']) img = await page.evaluate(f'''() => [...document.querySelectorAll('{whatsapp_selectors_dict['media_url_img']}')] .map(element => element.src)''') if img and len(img) == 2: img = img[-1] if img not in media_arr: media_arr['img'].append(img) break countTry += 1 time.sleep(0.3) except Exception as e: # If media is a video or gif countTry = 0 while True: vid = await page.evaluate(f'''() => [...document.querySelectorAll('{whatsapp_selectors_dict['media_url_vid']}')] .map(element => element.src)''') if vid: vid = vid[-1] media_arr['vid'].append(vid) break # Go to next media element await page.waitForSelector(whatsapp_selectors_dict['left_arrow_button']) await page.evaluate(f'''document.querySelector('{whatsapp_selectors_dict['left_arrow_button']}').click()''') time.sleep(0.5) except Exception as e: print(e) count = 0 newPage = await browser.newPage() # Downloading media for image in media_arr['img']: try: count += 1 print(image) viewSource = await newPage.goto(image) f = open(media_path / f'{count}.jpg', 'wb') f.write(await viewSource.buffer()) f.close() except Exception as e: print("Error saving image", e) for video in media_arr['vid']: try: count += 1 viewSource = await newPage.goto(video) f = open(media_path / f'{count}.mp4', 'wb') f.write(await viewSource.buffer()) f.close() except Exception as e: print("Error saving video", e) print("Saved the media to the 'media' dir") time.sleep(10) ================================================ FILE: wplay/get_media.py ================================================ # region Imports import time from pathlib import Path from wplay.utils import browser_config from wplay.utils.Logger import Logger from wplay.utils.helpers import profile_photos_path # endregion # region LOGGER __logger = Logger(Path(__file__).name) # endregion async def get_profile_photos(): """ Download the profile picture of all the contacts. """ page, _ = await browser_config.configure_browser_and_load_whatsapp() total_contacts = int(input("Please provide total whatsapp contacts: ")) loop = round(total_contacts/7) images_list = [] await page.waitForSelector('#pane-side > div:nth-child(1) > div > div > div:nth-child(1) > div > div > div > div > img') for c in range(loop): for i in range(1, 18): selector = f"#pane-side > div:nth-child(1) > div > div > div:nth-child({i}) > div > div > div > div > img" try: await page.waitForSelector(selector, timeout=2000) image_url = await page.evaluate(f'document.querySelector("{selector}").getAttribute("src")') print(f"{c}:{i}-{image_url}") if image_url not in images_list: images_list.append(image_url) except Exception as e: print(e) print("No profile image found") await page.evaluate("document.querySelector('#pane-side').scrollBy(0, 500)") for count in range(len(images_list)): try: viewSource = await page.goto(images_list[count]) f = open(profile_photos_path / f'{count}.jpg', 'wb') f.write(await viewSource.buffer()) f.close() except Exception as e: print("Error saving image") print("Saved all the images to media_folder.") time.sleep(5) ================================================ FILE: wplay/get_news.py ================================================ # region IMPORTS from pathlib import Path import time from newsapi.newsapi_client import NewsApiClient from wplay.utils import browser_config from wplay.utils import target_search from wplay.utils import target_select from wplay.utils import io from wplay.utils.Logger import Logger # endregion # region LOGGER __logger = Logger(Path(__file__).name) # endregion ''' Visit https://newsapi.org/ to get your own API key. ''' newsapi = NewsApiClient(api_key="YOUR API KEY") async def get_news(target): """ Sends news as a message in every two minutes. """ def fetch_news(country_code): """ Return the title and url of the news. """ headlines = newsapi.get_top_headlines(country=country_code, language='en') url = headlines['articles'][0]['url'] title = headlines['articles'][0]['title'] return title, url page, _ = await browser_config.configure_browser_and_load_whatsapp() if target is not None: try: await target_search.search_and_select_target(page, target) except Exception as e: print(e) await target_search.search_and_select_target_without_new_chat_button(page, target) else: await target_select.manual_select_target(page) country = input("Enter your country code (ex: us or in): ") while True: try: news, source = fetch_news(country) news_ = f"*{news}* \n Full News : {source}" await io.send_message(page, news_) except Exception as e: print("Unable to get the news", e) time.sleep(120) # Sends news in every 2 min ================================================ FILE: wplay/message_blast.py ================================================ # region IMPORTS from pathlib import Path from typing import List from wplay.utils import browser_config from wplay.utils import target_search from wplay.utils import target_select from wplay.utils import io from wplay.utils.Logger import Logger from wplay.utils.helpers import logs_path # endregion # region LOGGER __logger = Logger(Path(__file__).name) # endregion async def message_blast(target: str): """ Sends n number of messages to the target person. """ page, _ = await browser_config.configure_browser_and_load_whatsapp() if target is not None: await target_search.search_and_select_target_all_ways(page, target) else: await target_select.manual_select_target(page) message: List[str] = io.ask_user_for_message_breakline_mode() number_of_messages: int = int(input("Enter the number of messages to blast: ")) __logger.debug("Blasting messages") for _ in range(number_of_messages): await io.send_message(page, message) ================================================ FILE: wplay/message_service.py ================================================ # region IMPORTS from pathlib import Path import threading import time import json from wplay.utils import browser_config from wplay.utils.target_search import search_target_by_number from wplay.utils import target_select from wplay.utils import io from wplay.utils import helpers from wplay.utils import verify_internet from wplay.utils.Logger import Logger from wplay.utils.MessageStack import MessageStack # endregion # region LOGGER import logging __logger = Logger(Path(__file__).name, logging.DEBUG) # endregion """ Messages file structure, your program should add messages this way inside the file "messages.json" located at user/wplay/messagesJSON folder. { "messages": [ { "uuid": "33bf7c667f8011ea96971c3947562893", "number": "5562999999999", "message": "*Bold Hello World*" }, { "uuid": "46ca6d284f8058ee89354e2987862869", "number": "5562888888888", "message": ["Hello!!!","Multi-line"] } ] } """ async def message_service(): page, _ = await browser_config.configure_browser_and_load_whatsapp() __logger.info("Message Service On.") print("Message Service is ON, press CTRL+C to stop.") print("Listening for messages in file 'messages.json' inside user/wplay/messagesJSON folder.") # Initialize a instance of MessageStack message_stack = MessageStack() # Move all messages from open_messages.json to messages.json when the program starts message_stack.move_all_messages(helpers.open_messages_json_path, helpers.messages_json_path) while True: if verify_internet.internet_avalaible(): try: # Try to get the message current_msg = next(message_stack.get_message()) # Move message from messages.json to open_messages.json message_stack.move_message(helpers.messages_json_path, helpers.open_messages_json_path, current_msg['uuid']) try: if await search_target_by_number(page, current_msg['number']): await io.send_message(page, current_msg['message']) message_stack.remove_message(current_msg['uuid'], helpers.open_messages_json_path) except ValueError: __logger.debug("Wrong JSON Formatting. Message Deleted.") message_stack.remove_message(current_msg['uuid'], helpers.open_messages_json_path) except Exception as e: # If any error occurs that is not because of wrong data, # the message will be moved back to messages.json __logger.error(f'Error handling and sending the message: {str(e)}') MessageStack().move_message(helpers.open_messages_json_path, helpers.messages_json_path, current_msg['uuid']) except (StopIteration, json.JSONDecodeError): # if there are no messages to catch we will have this 'Warning', will try again after a time time.sleep(1) else: __logger.debug('Internet is not available, trying again after 15 seconds.') time.sleep(15) # Move messages from open_messages.json to messages.json that wasn't sended. message_stack.move_all_messages(helpers.open_messages_json_path, helpers.messages_json_path) ================================================ FILE: wplay/message_timer.py ================================================ # region IMPORTS import time import random from pathlib import Path from wplay.utils import browser_config from wplay.utils import target_search from wplay.utils import target_select from wplay.utils import io from wplay.utils.Logger import Logger from wplay.utils.helpers import logs_path # endregion # region LOGGER __logger = Logger(Path(__file__).name) # endregion async def message_timer(target): """ Sends message in a particular time interval. """ page, _ = await browser_config.configure_browser_and_load_whatsapp() if target is not None: try: await target_search.search_and_select_target(page, target) except Exception as e: print(e) await page.reload() await target_search.search_and_select_target_without_new_chat_button(page, target) else: await target_select.manual_select_target(page) # Region inputs __logger.info("Input message information for message timer") message_type_numbers: int = int(input("How many types of messages will you send? ")) messages: list[str] = list() for _ in range(message_type_numbers): messages.append(io.ask_user_for_message_breakline_mode()) number_of_messages: int = int(input("Enter the number of messages to send: ")) minimumTimeInterval: int = int(input("Enter minimum interval number in seconds: ")) maximumTimeInterval: int = int(input("Enter maximum interval number in seconds: ")) # Endregion random.seed() for _ in range(number_of_messages): if not messages: break await io.send_message(page, messages[random.randrange(0, message_type_numbers)]) if minimumTimeInterval != maximumTimeInterval: time.sleep(random.randrange(minimumTimeInterval, maximumTimeInterval)) else: time.sleep(minimumTimeInterval) ================================================ FILE: wplay/online_tracker.py ================================================ import time from pathlib import Path from datetime import datetime from playsound import playsound from wplay.utils import browser_config from wplay.utils import target_search from wplay.utils import target_select from wplay.utils import target_data from wplay.utils.helpers import tracking_folder_path from wplay.utils.Logger import Logger from wplay.utils.helpers import logs_path # region LOGGER __logger = Logger(Path(__file__).name) # endregion async def tracker(target): """ This function checks the online and offline status of the target person. """ page, _ = await browser_config.configure_browser_and_load_whatsapp() # open bot browser and load whatsapp web website if target is not None: # checks if target is not none then it search for the target and select it try: target_name = await target_search.search_and_select_target(page, target, hide_groups=True) except Exception as e: print(e) await page.reload() target_name = await target_search.search_and_select_target_without_new_chat_button(page, target, hide_groups=True) else: # if target is none then it allow user to select target manually from browser target_name = await target_select.manual_select_target(page, hide_groups=True) # opens status file of the target person status_file: str = open(tracking_folder_path / f'status_{target_name}.txt', 'w').close() status_file: str = open(tracking_folder_path / f'status_{target_name}.txt', 'a') # default assignes is_sound_enabled: bool = True last_status: str = 'offline' try: print(f'Tracking: {target_name}') __logger.info("Tracking target") status_file.write(f'Tracking: {target_name}\n') while True: status: str = await target_data.get_last_seen_from_focused_target(page) # checks last seen if status == 'online': # if last seen is online then shows online is_online: bool = True else: # if nothing is there so shows offline is_online: bool = False status: str = 'offline' # play a notification sound on online if last_status != is_online: if is_online: try: if is_sound_enabled: playsound('plucky.wav') except Exception as e: print("Error: Couldn't play the sound.") is_sound_enabled: bool = False print( f'{datetime.now().strftime("%d/%m/%Y, %H:%M:%S")}' + f' - Status: {status}' ) status_file.write( f'{datetime.now().strftime("%d/%m/%Y, %H:%M:%S")}' + f' - Status: {status}\n') last_status: str = is_online time.sleep(0.5) except KeyboardInterrupt: __logger.error("User Pressed Ctrl+C") finally: # save the status and close the file status_file.close() print(f'\nStatus file saved in: {str(tracking_folder_path/"status_")}{target_name}.txt') ================================================ FILE: wplay/profile_download.py ================================================ # region IMPORTS from pathlib import Path from wplay.utils import browser_config from wplay.utils import target_search from wplay.utils import target_select from wplay.utils.helpers import profile_photos_path from wplay.utils.helpers import whatsapp_selectors_dict from wplay.utils.Logger import Logger # endregion # region LOGGER __logger = Logger(Path(__file__).name) # endregion async def get_profile_picture(target): page, _ = await browser_config.configure_browser_and_load_whatsapp() if target is not None: try: await target_search.search_and_select_target(page, target) except Exception as e: print(e) await page.reload() await target_search.search_and_select_target_without_new_chat_button(page, target) else: target = await target_select.manual_select_target(page) # Getting Profile picture url selector = '#main > header > div > div > img' await page.waitForSelector(selector, timeout=2000) image_url = await page.evaluate(f'document.querySelector("{selector}").getAttribute("src")') try: viewSource = await page.goto(image_url) f = open(profile_photos_path / f'{target}.jpg', 'wb') f.write(await viewSource.buffer()) f.close() except Exception as e: print("Error saving image") ================================================ FILE: wplay/save_chat.py ================================================ # region IMPORTS from pathlib import Path from wplay.utils import browser_config from wplay.utils import target_search from wplay.utils import target_select from wplay.utils.helpers import save_chat_folder_path from wplay.utils.Logger import Logger # endregion # region LOGGER __logger = Logger(Path(__file__).name) # endregion async def save_chat(target): """ Save the whole chat of the target person in .txt file. """ page, _ = await browser_config.configure_browser_and_load_whatsapp() if target is not None: try: await target_search.search_and_select_target(page, target) except Exception as e: print(e) await page.reload() await target_search.search_and_select_target_without_new_chat_button(page, target) else: target = await target_select.manual_select_target(page) # selectors selector_values = "#main > div > div > div > div > div > div > div > div" selector_sender = "#main > div > div > div > div > div > div > div > div > div.copyable-text" # Getting all the messages of the chat try: __logger.info("Saving chats with target") await page.waitForSelector(selector_values) values = await page.evaluate(f'''() => [...document.querySelectorAll('{selector_values}')] .map(element => element.textContent)''') sender = await page.evaluate(f'''() => [...document.querySelectorAll('{selector_sender}')] .map(element => element.getAttribute("data-pre-plain-text"))''') final_values = [x[:-8] for x in values] new_list = [a + b for a, b in zip(sender, final_values)] # opens chat file of the target person with open(save_chat_folder_path / f'chat_{target}.txt', 'w') as output: for s in new_list: output.write("%s\n" % s) except Exception as e: print(e) finally: # save the chat and close the file output.close() print(f'\nChat file saved in: {str(save_chat_folder_path/"chat_")}{target}.txt') ================================================ FILE: wplay/schedule_message.py ================================================ # region IMPORTS from datetime import datetime from pathlib import Path import time import sys from wplay.utils import browser_config from wplay.utils import target_search from wplay.utils import target_select from wplay.utils import io from wplay.utils.Logger import Logger # endregion # region LOGGER __logger = Logger(Path(__file__).name) # endregion async def schedule_message(target): """ Sends message to the target person at a scheduled time. """ page, _ = await browser_config.configure_browser_and_load_whatsapp() if target is not None: await target_search.search_and_select_target(page, target) else: await target_select.manual_select_target(page) time_ = input("Enter the schedule time in HH:MM:SS format-> ") hour, minute, second = time_.split(':') current_time = datetime.now() delta_hour: int = int(hour) - current_time.hour delta_min: int = int(minute) - current_time.minute delta_second: int = int(second) - current_time.second total_seconds: int = delta_hour*3600 + delta_min*60 + delta_second if total_seconds < 0: print("Current time is ahead of the scheduled time") sys.exit() message: list[str] = io.ask_user_for_message_breakline_mode() print("Your message is scheduled at : ", time_) time.sleep(total_seconds) await io.send_message(page, message) ================================================ FILE: wplay/settings.cfg ================================================ [auth] gmail = alias@gmail.com passw = yourpassword devid = 0000000000000000 [app] pkg = com.whatsapp sig = 38a0f7d505fe18fec64fbf343ecaaaf310dbd799 [client] pkg = com.google.android.gms sig = 38918a453d07199354f8b19af05ec6562ced5788 ver = 9877000 ================================================ FILE: wplay/target_info.py ================================================ # region IMPORTS from wplay.utils import browser_config from wplay.utils import target_search from wplay.utils import target_select import phonenumbers from phonenumbers import carrier from phonenumbers import geocoder from phonenumbers import timezone import re import sys from pathlib import Path from wplay.utils.Logger import Logger # end IMPORTS # region LOGGER __logger = Logger(Path(__file__).name) # endregion def formatNumber(InputNumber): return re.sub(r"(?:\+)?(?:[^[0-9]*)", "", InputNumber) def localScan(InputNumber, print_results=True): print("Running local scan...") FormattedPhoneNumber = "+" + formatNumber(InputNumber) try: PhoneNumberObject = phonenumbers.parse(FormattedPhoneNumber, None) except Exception as e: print(e) else: if not phonenumbers.is_valid_number(PhoneNumberObject): return False number = phonenumbers.format_number(PhoneNumberObject, phonenumbers.PhoneNumberFormat.E164).replace("+", "") numberCountryCode = phonenumbers.format_number(PhoneNumberObject, phonenumbers.PhoneNumberFormat.INTERNATIONAL).split(" ")[0] numberCountry = phonenumbers.region_code_for_country_code(int(numberCountryCode)) localNumber = phonenumbers.format_number(PhoneNumberObject, phonenumbers.PhoneNumberFormat.E164).replace(numberCountryCode, "") internationalNumber = phonenumbers.format_number(PhoneNumberObject, phonenumbers.PhoneNumberFormat.INTERNATIONAL) country = geocoder.country_name_for_number(PhoneNumberObject, "en") location = geocoder.description_for_number(PhoneNumberObject, "en") carrierName = carrier.name_for_number(PhoneNumberObject, "en") if print_results: print("International format: {}".format(internationalNumber)) print("Local format: {}".format(localNumber)) print("Country found: {} ({})".format(country, numberCountryCode)) print("City/Area: {}".format(location)) print("Carrier: {}".format(carrierName)) for timezoneResult in timezone.time_zones_for_number(PhoneNumberObject): print("Timezone: {}".format(timezoneResult)) if phonenumbers.is_possible_number(PhoneNumberObject): print("The number is valid and possible.") else: print("The number is valid but might not be possible.") numberObj = {} numberObj["input"] = InputNumber numberObj["default"] = number numberObj["local"] = localNumber numberObj["international"] = internationalNumber numberObj["country"] = country numberObj["countryCode"] = numberCountryCode numberObj["countryIsoCode"] = numberCountry numberObj["location"] = location numberObj["carrier"] = carrierName return numberObj def scanNumber(InputNumber): print("[!] ---- Fetching informations for {} ---- [!]".format(formatNumber(InputNumber))) number = localScan(InputNumber) if not number: print(("Error: number {} is not valid. Skipping.".format(formatNumber(InputNumber)))) sys.exit() print("Scan finished.") def target_contact_number(num): target_contact_number.phone_number = num async def target_info(target): page, _ = await browser_config.configure_browser_and_load_whatsapp() if target is not None: try: await target_search.search_and_select_target(page, target) except Exception as e: print(e) await page.reload() await target_search.search_and_select_target_without_new_chat_button(page, target) else: await target_select.manual_select_target(page) """ # to find location by ip address print('Get you ipinfo token from https://ipinfo.io/account') ip_address = '*' token = str(input("Enter your ipinfo token: ")) ip_string = 'curl ipinfo.io/'+ip_address+'?token='+token+'' os.system(ip_string) """ __logger.info("Writing target's information") scanNumber(target_contact_number.phone_number) ================================================ FILE: wplay/telegram_bot.py ================================================ # region IMPORTS import tkinter from tkinter import filedialog from pathlib import Path import pickle from telegram.ext import CommandHandler, Updater from wplay.utils.helpers import data_folder_path from wplay.utils.Logger import Logger # endregion # region LOGGER __logger = Logger(Path(__file__).name) # endregion status_file_path = None def start_tkinter(): root_window = tkinter.Tk() root_window.withdraw() def ask_where_are_the_status_file(): print('Choose a status text file.') status_file_path = filedialog.askopenfile( initialdir=data_folder_path / 'tracking_data', title='Choose a status text file.', filetypes=(("text files", "*.txt"), ("all files", "*.*")) ) if status_file_path == (): print("Error! Choose a status.") exit() return status_file_path def startmessage(bot, update): chat_id: int = update.message.chat_id text: str = ''' Hi, I am here to send all tracked online status in whatsapp :) ''' bot.send_message(chat_id=chat_id, text=text) def send_status(bot, update): # Display last updated online status message chat_id = update.message.chat_id try: f = open(status_file_path, 'r') file_data = f.readlines() text: Union[str, bytes] = file_data[len(file_data) - 1] bot.send_message(chat_id=chat_id, text=text) except Exception as e: print(e) bot.send_message(chat_id=chat_id, text='oops! An error occurred') def telegram_status(name): print(name) start_tkinter() global status_file_path status_file_path = ask_where_are_the_status_file() # Add bot token global TOKEN new_token = False token_file_path = "wplay/telegram_token.pkl" if Path(token_file_path).exists(): user_choice = input("Do you want to use last saved token (Y) or enter new token (N): ") if user_choice in "Yy": with open(token_file_path, "rb") as token_file: TOKEN = pickle.load(token_file) else: new_token = True else: new_token = True if new_token: TOKEN = input("Enter token: ") with open(token_file_path, "wb") as token_file: pickle.dump(TOKEN, token_file) # Added all the essential command handlers updater = Updater(TOKEN, use_context=True) dp = updater.dispatcher dp.add_handler(CommandHandler('start', startmessage)) dp.add_handler(CommandHandler('status', send_status)) updater.start_polling() updater.idle() ================================================ FILE: wplay/terminal_chat.py ================================================ # region IMPORTS from pathlib import Path from wplay.utils import browser_config from wplay.utils import target_search from wplay.utils import target_select from wplay.utils import io from wplay.chat_intermediator import intermediary from wplay import text_to_speech from wplay.utils.Logger import Logger from wplay.utils.helpers import logs_path from colorama import Fore, Style from wplay import chatbot # endregion # region LOGGER __logger = Logger(Path(__file__).name) # endregion async def chat(target): __logger.info("Chatting with target") page, _ = await browser_config.configure_browser_and_load_whatsapp() if target is not None: try: await target_search.search_and_select_target(page, target) except Exception as e: print(e) await page.reload() await target_search.search_and_select_target_without_new_chat_button(page, target) else: target = await target_select.manual_select_target(page) # selectors selector_values = "#main > div > div > div > div > div > div > div > div" selector_sender = "#main > div > div > div > div > div > div > div > div > div.copyable-text" # Getting all the messages of the chat try: __logger.info("Printing recent chat") await page.waitForSelector(selector_values) values = await page.evaluate(f'''() => [...document.querySelectorAll('{selector_values}')] .map(element => element.textContent)''') sender = await page.evaluate(f'''() => [...document.querySelectorAll('{selector_sender}')] .map(element => element.getAttribute("data-pre-plain-text"))''') new_values = [x[:-8] for x in values] new_list = [a + b for a, b in zip(sender, new_values)] final_list = new_list[-6:] for s in final_list: print("%s\n" % s) except Exception as e: print(e) print("\033[91m {}\033[00m".format("\nType '...' in a new line or alone in the message to change target person.\nType '#_FILE' to send Image/Video/Documentd etc.\nType '#_TTS' to convert text to speech and send audio file.\nType '#_FWD' to foward your last message received")) while True: await getMessages(page, target) message: list[str] = io.ask_user_for_message_breakline_mode() # Target Change if "..." in message: message.remove('...') await io.send_message(page, message) target = input("\n\nNew Target Name: ") if target is not None: await target_search.search_and_select_target(page, target) else: await target_select.manual_select_target(page) # Be an Intermediator if "#_FWD" in message: await target_search.search_and_select_target(page, intermediary.rec) await io.send_message(page, getMessages.foward_message) message = io.ask_user_for_message_breakline_mode() # Text to speech if "#_TTS" in message: await text_to_speech.text_to_speech(target) await io.send_file(page) # File Share: if "#_FILE" in message: message.remove("#_FILE") await io.send_file(page) await getMessages(page, target) await io.send_message(page, message) async def getMessages(page, target): """ Get the last messages of the chats. """ # selectors selector_values = "#main > div > div > div > div > div > div > div > div" selector_sender = "#main > div > div > div > div > div > div > div > div > div.copyable-text" try: # Getting all the messages of the chat await page.waitForSelector(selector_values) values = await page.evaluate(f'''() => [...document.querySelectorAll('{selector_values}')] .map(element => element.textContent)''') sender = await page.evaluate(f'''() => [...document.querySelectorAll('{selector_sender}')] .map(element => element.getAttribute("data-pre-plain-text"))''') lastMessage = values[-1] last_message_time = sender[-1].split(',') last_message_time = last_message_time[0].replace('[', '') lastMessage = lastMessage.replace(last_message_time, '') except Exception as e: print(e) lastMessage = "" lastOutgoingMessage = "" if lastOutgoingMessage != lastMessage: print(Fore.GREEN + f"{target}-", end="") print(lastMessage, end="") print(Style.RESET_ALL) getMessages.foward_message = lastMessage if "/image" in lastMessage: bot_msg = await chatbot.Bot(last_Message=lastMessage) await io.send_message(page, bot_msg) await io.send_file(page) elif lastMessage[0] == "/": bot_msg = await chatbot.Bot(last_Message=lastMessage) await io.send_message(page, bot_msg) lastOutgoingMessage = lastMessage ================================================ FILE: wplay/text_to_speech.py ================================================ # region IMPORTS from pathlib import Path from wplay.utils.helpers import audio_file_folder_path from wplay.utils.Logger import Logger from gtts import gTTS # endregion # region LOGGER __logger = Logger(Path(__file__).name) # endregion async def text_to_speech(target): try: __logger.info("Converting text to speech audio file") # The text that you want to convert to audio text = input("\n\nWrite the text you want to convert to audio file: ") list_laguages = ['bn: Bengali', 'de: German', 'en: English', 'es: Spanish', 'fr: French', 'gu: Gujarati', 'hi: Hindi', 'it: Italian', 'ja: Japanese', 'kn: Kannada', 'ko: Korean', 'ml: Malayalam', 'mr: Marathi', 'pt-br: Portuguese (Brazil)', 'ru: Russian', 'ta: Tamil', 'te: Telugu', 'ur: Urdu'] print('Choose a code for language of your choice from the following list\n') print(list_laguages) # Language in which you want to convert language = input("\n\nEnter the language you want to convert to audio file: ") # Passing the text and language to the engine, myobj = gTTS(text=text, lang=language, slow=False) # Saving the converted audio in a mp3 file named myobj.save(audio_file_folder_path / "{}.mp3".format(target)) except Exception as e: print(e) finally: print('\nAudio file saved in: {}/ {}.mp3'.format(audio_file_folder_path, target)) ================================================ FILE: wplay/utils/Logger.py ================================================ # region IMPORTS import logging from pathlib import Path from wplay.utils.helpers import log_file_path, logs_path, test_log_file_path # endregion class Logger: if not (log_file_path).exists(): logs_path.mkdir(parents=True, exist_ok=True) open(log_file_path, 'w').close() if not (test_log_file_path).exists(): logs_path.mkdir(parents=True, exist_ok=True) open(test_log_file_path, 'w').close() def __init__(self, script_name: str, level: int = logging.WARNING): self.logger = logging.getLogger(script_name) self.level = level self.logger.setLevel(self.level) if not self.logger.handlers: # Create handlers file_handler = logging.FileHandler(log_file_path) file_handler = logging.FileHandler(test_log_file_path) console = logging.StreamHandler() file_handler.setLevel(self.level) console.setLevel(self.level) # create formatter and add it to the handlers formatter = logging.Formatter('%(name)s - %(levelname)s - %(message)s') console.setFormatter(formatter) file_handler.setFormatter(formatter) # add the handlers to logger self.logger.addHandler(console) self.logger.addHandler(file_handler) def debug(self, msg: str): self.logger.debug(msg) def error(self, msg: str): self.logger.error(msg) def info(self, msg: str): self.logger.info(msg) ================================================ FILE: wplay/utils/MessageStack.py ================================================ # region IMPORTS from pathlib import Path from typing import List, Iterator import json from wplay.utils import helpers from wplay.utils.Logger import Logger # endregion class MessageStack(): def __init__(self): self.logger = Logger(Path(__file__).name) self.__create_json_file(helpers.messages_json_path) self.__ensure_valid_json(helpers.messages_json_path) self.__create_json_file(helpers.open_messages_json_path) self.__ensure_valid_json(helpers.open_messages_json_path) def __create_json_file(self, file_path: Path): if not file_path.is_file(): open(file_path, 'w').close() self.logger.info(f'{file_path.name} created.') def __write_json(self, data: dict, file_path: Path): with open(file_path, "w") as json_file: json.dump(data, json_file, indent=4) def __ensure_valid_json(self, file_path: Path): valid_data = {"messages": list()} try: with open(file_path) as json_file: data = json.load(json_file) if 'messages' not in data: self.__write_json(valid_data, file_path) except json.JSONDecodeError: # Empty or Invalid Json self.__write_json(valid_data, file_path) def append_message(self, message: dict, file_path: Path): """ Append messages into json file. Arguments: message -- dict with a message file_path {Path} -- open_messages_json_path or messages_json_path from helpers """ self.__ensure_valid_json(file_path) with open(file_path) as json_file: json_data = json.load(json_file) json_data['messages'].append(message) self.__write_json(json_data, file_path) self.logger.info(f'Message appended to {file_path.name}') def get_message( self, from_file_path: Path = helpers.messages_json_path) -> Iterator[dict]: """ Yield a message from a file. Arguments: from_file_path {Path} -- open_messages_path or messages_path from helpers Exception: raise StopIteration, json.JSONDecodeError, KeyError if file is empty, or the iteration stopped or the key isn't finded. Yields: [dict] -- Yield a dict with all message data """ with open(from_file_path) as json_file: data = json.load(json_file) for message in data['messages']: yield message def get_all_messages( self, from_file_path: Path = helpers.messages_json_path) -> List[dict]: self.__ensure_valid_json(from_file_path) with open(from_file_path) as json_file: data = json.load(json_file) return data['messages'] def move_message(self, from_file_path: Path, to_file_path: Path, uuid: str): with open(from_file_path) as json_file: data = json.load(json_file) for message in data['messages']: if uuid in message['uuid']: self.append_message(message, to_file_path) self.remove_message(uuid, from_file_path) def move_all_messages(self, from_file_path: Path, to_file_path: Path): self.__ensure_valid_json(from_file_path) with open(from_file_path) as json_file: data = json.load(json_file) for message in data['messages']: self.append_message(message, to_file_path) self.remove_message(message['uuid'], from_file_path) def remove_message(self, uuid: str, file_path: Path): with open(file_path) as json_file: data = json.load(json_file) for i, message in enumerate(data['messages']): if uuid in message['uuid']: del data['messages'][i] self.__write_json(data, file_path) self.logger.info(f"Message Deleted Successfully from {file_path.name}") def remove_all_messages(self, file_path: Path): with open(file_path) as json_file: data = json.load(json_file) for i, message in enumerate(data['messages']): del data['messages'][i] self.__write_json(data, file_path) self.logger.info(f"Deleted Successfully all messages from {file_path.name}") ================================================ FILE: wplay/utils/SessionManager.py ================================================ # region Imports import os import stat import shutil from pathlib import Path from whaaaaat import Separator, prompt from transitions import Machine, State from wplay.utils.helpers import user_data_folder_path from wplay.utils.helpers import menu_style # endregion states = [ State(name='start'), State(name='get_user_data_filenames', on_enter='get_user_data_filenames'), State(name='prepare_questions', on_enter='prepare_questions'), State(name='get_answer_menu', on_enter='get_answer_menu'), State(name='verify_answers', on_enter='verify_answers')] transitions = [ {'trigger': 'start', 'source': '*', 'dest': 'start'}, {'trigger': 'get_user_data_filenames', 'source': 'start', 'dest': 'get_user_data_filenames'}, {'trigger': 'prepare_questions', 'source': 'get_user_data_filenames', 'dest': 'prepare_questions'}, {'trigger': 'get_answer_menu', 'source': 'prepare_questions', 'dest': 'get_answer_menu'}, {'trigger': 'verify_answers', 'source': 'get_answer_menu', 'dest': 'verify_answers'}] class SessionManager(object): def __init__(self): self.data_filenames = None # type : list self.questions_menu = None # type : list self.question_overwrite = None # type : list self.answers_menu = None # type : dict self.username = None # type : str self.save_session = False # type : bool self.user_options = { 'restore': 'Restore a session', 'save': 'Create a new session', 'continue': 'Continue without saving', 'delete': 'Delete a session', 'exit': 'Exit' } # type : dict def reset_fields(self): self.data_filenames = None # type : list self.questions_menu = None # type : list self.question_overwrite = None # type : list self.answers_menu = None # type : dict self.username = None # type : str self.save_session = False # type : bool def get_user_data_filenames(self): self.data_filenames = [file.stem for file in user_data_folder_path.glob('*')] def prepare_questions(self): self.questions_menu = [ { 'type': 'rawlist', 'name': 'user_options', 'message': '***Session Manager***:', 'choices': [ Separator(), self.user_options['restore'], self.user_options['save'], self.user_options['continue'], Separator(), self.user_options['delete'], self.user_options['exit'], Separator() ] }, { 'type': 'rawlist', 'name': 'restore', 'message': 'Select a session to try to restore:', 'choices': [*[session for session in self.data_filenames], '<---Go-back---'], 'when': lambda answers: answers['user_options'] == self.user_options['restore'] }, { 'type': 'input', 'name': 'save', 'message': 'Write your first name or username to save:', 'when': lambda answers: answers['user_options'] == self.user_options['save'] }, { 'type': 'checkbox', 'name': 'delete', 'message': 'Mark the sessions you want to delete:', 'choices': list(map(lambda e: {'name': e}, self.data_filenames)), 'when': lambda answers: answers['user_options'] == self.user_options['delete'] } ] self.question_overwrite = [ { 'type': 'confirm', 'name': 'overwrite_data', 'message': 'There is already a session with that name, overwrite it?', 'default': True } ] def get_answer_menu(self): self.answers_menu = prompt(self.questions_menu, style=menu_style) def verify_answers(self): # Handle when person choose 'Restore a session' if self.answers_menu['user_options'] == self.user_options['restore']: if self.answers_menu['restore'] == '<---Go-back---': return False else: self.username = self.answers_menu['restore'] self.save_session = True return True # Handle when person choose 'Create a new session' elif self.answers_menu['user_options'] == self.user_options['save']: self.username = self.answers_menu['save'] self.save_session = True return self.__verify_if_session_file_exists() # Handle when person choose 'Continue without saving' elif self.answers_menu['user_options'] == self.user_options['continue']: self.username, self.save_session = None, False return True # Handle when person choose 'Delete a session' elif self.answers_menu['user_options'] == self.user_options['delete']: if len(self.answers_menu['delete']) > 0: [self.__delete_session_data(user_data_folder_path / username) for username in self.answers_menu['delete']] return False # Handle when person choose 'Exit' elif self.answers_menu['user_options'] == self.user_options['exit']: exit() def __verify_if_session_file_exists(self) -> bool: if self.username in self.data_filenames: answer_overwrite = prompt(self.question_overwrite, style=menu_style) if answer_overwrite['overwrite_data']: self.__delete_session_data(user_data_folder_path / self.username) return answer_overwrite['overwrite_data'] return True def __delete_session_data(self, path): def handleError(func, path, exc_info): print('Handling Error for file ', path) if not os.access(path, os.W_OK): print('Trying to change permission!') os.chmod(path, stat.S_IWUSR) shutil.rmtree(path, ignore_errors=True) shutil.rmtree(path, onerror=handleError) @staticmethod def session_manager(): done = False obj = SessionManager() while(not done): if obj.questions_menu is not None: obj.reset_fields() machine = Machine(obj, states, transitions=transitions, initial='start') obj.get_user_data_filenames() obj.prepare_questions() obj.get_answer_menu() done = obj.verify_answers() return obj.username, obj.save_session ================================================ FILE: wplay/utils/TODO ================================================ #TODO all code: return browser.close and use it instead use exit #TODO target_search: Add a whaaaaaat menu #FIXME target_search: False positive group -> Groups name use the same div as contact status, so we need to verify if target name is in title, not in status. But, sometimes the status contain the target name and shows up as false-positive group. WHERE WE CAN FIX? __checking_group_list #TODO io: Wait for the last message to be sent before closing the browser ================================================ FILE: wplay/utils/__init__.py ================================================ ================================================ FILE: wplay/utils/browser_config.py ================================================ # region TUTORIAL ''' Go to region 'FOR SCRIPTING' and use the methods in your script! EXAMPLE OF USAGE: from wplay.pyppeteerUtils import pyppeteerConfig as pypConfig from wplay.pyppeteerUtils import pyppeteerSearch as pypSearch async def my_script(target): pages, browser = wait pyp.configure_browser_and_load_whatsapp( pypConfig.websites['whatsapp'] ) await pypSearch.search_for_target_and_get_ready_for_conversation( pages[0], target ) message = pypSearch.ask_user_for_message_breakline_mode() await pypSearch.send_message(pages[0], message) message2 = pypSearch.ask_user_for_message() await pypSearch.send_message(pages[0], message2) ''' # endregion # region IMPORTS from typing import Any, List from pathlib import Path import websockets.client from pyppeteer import launch, connection, launcher from pyppeteer.browser import Browser from pyppeteer.page import Page from wplay.utils.SessionManager import SessionManager from wplay.utils.helpers import websites, user_data_folder_path from wplay.utils.Logger import Logger # endregion # region LOGGER __logger = Logger(Path(__file__).name) # endregion # region FOR SCRIPTING async def configure_browser_and_load_whatsapp() -> (Page, Browser): """ Configure browser, configure the first page and open whatsapp website. Returns: Page -- return the first page, with whatsapp open Browser -- return the browser object """ __patch_pyppeteer() username, save_session = SessionManager.session_manager() browser = await __config_browser(username, save_session) pages = await get_pages(browser) first_page = pages[0] await config_page(first_page) await load_website(first_page, websites['whatsapp']) return first_page, browser async def get_pages(browser: Browser) -> List[Page]: __logger.debug('Getting open pages') return await browser.pages() async def open_new_page(browser: Browser): """ Open a new tab. """ __logger.debug('Opening new page(tab)') await browser.newPage() async def config_page(page: Page): __logger.debug('Configuring page') await __set_user_agent(page) # await __set_view_port(page) async def load_website(page: Page, website: str): __logger.debug(f'Loading website: {website}') await page.bringToFront() await page.goto(website, waitUntil='networkidle2', timeout=0) def exit_if_wrong_url(page: Page, browser: Browser, url_to_check: str): if not page.url == url_to_check: __logger.error('Exit due to Wrong URL!') browser.close() exit() # endregion # region PYPPETEER PATCH # https://github.com/miyakogi/pyppeteer/pull/160 # HACK: We need this until this PR is accepted. Solves the bug bellow. # BUG:(Pyppeteer) The communication with Chromium are disconnected after 20s. def __patch_pyppeteer(): __logger.debug("Patching Pyppeteer.") class PatchedConnection(connection.Connection): def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) self._ws = websockets.client.connect( self._url, loop=self._loop, max_size=None, ping_interval=None, ping_timeout=None, ) connection.Connection = PatchedConnection launcher.Connection = PatchedConnection # endregion # region PYPPETEER PRIVATE FUNCTIONS async def __config_browser(username: str = None, save_session: bool = False): __logger.debug('Configuring Browser.') if username is not None and username.strip() != '' and save_session: return await launch(headless=False, autoClose=False, userDataDir=user_data_folder_path / username) else: return await launch(headless=False, autoClose=False) async def __set_user_agent(page: Page): await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36') async def __set_view_port(page: Page): await page.setViewport({'width': 1280, 'height': 800}) # endregion # region CODE THAT MIGHT BE USEFUL SOMEDAY ''' # FIX: # To load websites faster async def intercept(request, page_one, page_two): await page_one.setRequestInterception(True) await page_two.setRequestInterception(True) if any(request.resourceType == _ for _ in ('stylesheet', 'image', 'font', 'media')): await request.abort() else: await request.continue_() page.on('request', lambda req: asyncio.ensure_future(intercept(req))) ''' # endregion ================================================ FILE: wplay/utils/helpers.py ================================================ # region IMPORTS from pathlib import Path import signal import psutil from whaaaaat import style_from_dict, Token # endregion # region Whatsapp WEBSITES websites = {'whatsapp': 'https://web.whatsapp.com/', 'wpp_unknown': 'https://web.whatsapp.com/send?phone='} # endregion # region SELECTORS whatsapp_selectors_dict = { 'login_area': '#app > div > div > div.landing-header', 'new_chat_button': '#side > header div[role="button"] span[data-icon="chat"]', 'search_contact_input_new_chat': '#app > div > div > div > div > span > div > span > div > div > div > label > div > div', 'contact_list_elements_filtered_new_chat': '#app > div > div > div > div > span > div > span > div > div > div > div > div > div > div > div > div > div > div > span > span[title][dir]', 'group_list_elements_filtered_new_chat': '#app > div > div > div > div > span > div > span > div > div > div > div > div > div > div > div > div > div > div > div > span[title][dir]', 'search_contact_input': '#side > div > div > label > div > div', 'chat_list_elements_filtered': '#pane-side > div > div > div > div > div > div > div > div > div > span > span[title][dir]', 'target_focused_title': '#main > header div > div > span[title]', 'message_area': '#main > footer div.selectable-text[contenteditable]', 'last_seen': '#main > header > div > div > span[title]', 'target_chat_header': '#main > header', 'contact_info_page_elements': '#app > div > div > div:nth-child(2) > div:last-of-type > span > div > span > div > div > div:first-child', 'contact_info_page_group_element_heading': '#app > div > div > div:nth-child(2) > div:last-of-type > ' 'span > div > span > div > div:nth-child(5)>div>div>div>div:first-child>span', 'contact_info_page_group_elements': '#app > div > div > div:nth-child(2) > div:last-of-type > ' 'span > div > span > div > div:nth-child(5)>div:nth-child(2)>div>div', 'contact_info_page_close_button': '#app > div > div > div > div > span > div > span > div > header > div > div > button', 'chat_or_message_search': '#side > div:nth-child(3) > div > label > div > div:last-child', 'chats_groups_messages_elements': '#side > div:last-child > div > div > div > div', 'contact_element': 'span > span > span[class^="matched-text"]', 'group_element': 'div:last-child > div:first-child > div:first-child > div > span > span[class^="matched-text"]', 'attach_file': '#main > header > div > div > div:nth-child(2) > div', 'choose_file': '#main > header > div > div > div > span > div > div > ul > li:nth-child(3) > button', 'send_file': '#app > div > div > div > div > span > div > span > div > div > div > span > div > div > span', 'profile_photo_element': '#side > header > div > div > img', 'about_edit_button_element': '#app > div > div > div > div > span > div > div > div > div:nth-child(4) > div > div > span > div > span', 'about_text_area': '#app > div > div > div > div > span > div > div > div > div:nth-child(4) > div > div > div > div', 'contact_info_page_target_group_name_element': 'div:nth-child(2)>div>div> div:last-of-type', 'contact_info_page_target_group_creation_info_element': ':scope > div:last-child > span', 'contact_info_page_target_group_description_element': ':scope > div:last-child span:first-of-type', 'contact_info_page_target_group_member_elements': ':scope > div:nth-child(4) > div > div', 'invalid_number_ok_button': '#app > div > span> div > span > div > div > div > div > div > div > div', 'target_name_selector': "#main > header > div > div > div > span", 'media_text': "#app > div > div > div > div > span > div > span > div > div > div > div > div > div > div > div > span", 'media_images': "#app > div > div > div > div > span > div > span > div > div > span > div > div > div > div > div > div", 'left_arrow_button': "#app > div > span > div > div > div > div > div > span", 'media_url_img': "#app > div > span:nth-child(3) > div > div > div > div > div > div > div > div > img", 'media_url_vid': "#app > div > span:nth-child(3) > div > div > div > div > div > div > div > div > video", } # endregion # region PATHS data_folder_path = Path.home() / 'wplay' logs_path = data_folder_path / 'logs' log_file_path = logs_path / 'wplay.log' test_log_file_path = logs_path / 'testwplay.log' user_data_folder_path = data_folder_path / '.userData' profile_photos_path = data_folder_path / 'media' / 'profilePhotos' tracking_folder_path = data_folder_path / 'trackingData' messages_json_folder_path = data_folder_path / 'messagesJSON' / 'system' messages_json_path = data_folder_path / 'messagesJSON' / 'messages.json' open_messages_json_path = data_folder_path / 'messagesJSON' / 'system' / 'openMessages.json' media_path = data_folder_path / 'media' / 'media' save_chat_folder_path = data_folder_path / 'savedChats' audio_file_folder_path = data_folder_path / 'audioFiles' chatbot_image_folder_path = data_folder_path / 'ChatbotImage' # endregion # region MENU STYLES menu_style = style_from_dict({ Token.Separator: '#6C6C6C', Token.QuestionMark: '#FF9D00 bold', Token.Selected: '#5F819D', Token.Pointer: '#FF9D00 bold', Token.Instruction: '', # default Token.Answer: '#5F819D bold', Token.Question: '', }) # endregion # region FUNCTIONS def create_dirs(): logs_path.mkdir(parents=True, exist_ok=True) user_data_folder_path.mkdir(parents=True, exist_ok=True) profile_photos_path.mkdir(parents=True, exist_ok=True) tracking_folder_path.mkdir(parents=True, exist_ok=True) messages_json_folder_path.mkdir(parents=True, exist_ok=True) media_path.mkdir(parents=True, exist_ok=True) save_chat_folder_path.mkdir(parents = True, exist_ok = True) audio_file_folder_path.mkdir(parents = True, exist_ok = True) tracking_folder_path.mkdir(parents = True, exist_ok = True) messages_json_folder_path.mkdir(parents = True, exist_ok = True) chatbot_image_folder_path.mkdir(parents= True, exist_ok=True) def kill_child_processes(parent_pid, sig=signal.SIGTERM): try: parent = psutil.Process(parent_pid) except psutil.NoSuchProcess: return children = parent.children(recursive=True) print('Process Killed!') for process in children: process.send_signal(sig) # endregion ================================================ FILE: wplay/utils/io.py ================================================ # region IMPORTS from pathlib import Path from typing import List, Union from pyppeteer.page import Page from wplay.utils.helpers import whatsapp_selectors_dict from wplay.utils.Logger import Logger from wplay.utils.helpers import logs_path # endregion # region LOGGER __logger = Logger(Path(__file__).name) # endregion # region FOR SCRIPTING def ask_user_for_message() -> str: return str(input("Write your message: ")) def ask_user_for_message_breakline_mode() -> List[str]: message = list() i = 0 print("Write your message (Enter key to breakline)('.' alone to send):") while True: message.append(str(input())) if message[i] == '.': message.pop(i) break elif message[i] == '...' or message[i] == '#_FILE' or message[i] == '#_TTS' or message[i] == '#_FWD': break i += 1 return message async def send_message(page: Page, message: Union[List[str], str]): __logger.debug("Sending message") for i in range(len(message)): await page.type(whatsapp_selectors_dict['message_area'], message[i]) if isinstance(message, list): await page.keyboard.down('Shift') await page.keyboard.press('Enter') await page.keyboard.up('Shift') await page.keyboard.press('Enter') async def send_file(page): __logger.info("Sending File") await page.click(whatsapp_selectors_dict['attach_file']) await page.click(whatsapp_selectors_dict['choose_file']) await page.waitForSelector(whatsapp_selectors_dict['send_file'], timeout=30000) await page.click(whatsapp_selectors_dict['send_file']) # endregion ================================================ FILE: wplay/utils/target_data.py ================================================ # region IMPORTS from pathlib import Path from pyppeteer.page import Page from wplay.utils.helpers import whatsapp_selectors_dict from wplay.utils.Logger import Logger from wplay.utils.helpers import logs_path # endregion # region LOGGER __logger = Logger(Path(__file__).name) # endregion # region FOR SCRIPTING async def get_last_seen_from_focused_target(page: Page): __logger.info("Getting target's status information") # await page.waitForSelector(whatsapp_selectors_dict['status'], visible = True) try: status: str = await page.evaluate(f'document.querySelector("{whatsapp_selectors_dict["last_seen"]}").getAttribute("title")') return status except: return '#status not found' # endregion ================================================ FILE: wplay/utils/target_search.py ================================================ # region TUTORIAL ''' Go to region 'FOR SCRIPTING' and use the methods in your script! EXAMPLE OF USAGE: from wplay.pyppeteerUtils import pyppeteerConfig as pypConfig from wplay.pyppeteerUtils import pyppeteerSearch as pypSearch async def my_script(target): pages, browser = wait pyp.configure_browser_and_load_whatsapp(pypConfig.websites['whatsapp']) await pypSearch.search_for_target_and_get_ready_for_conversation(pages[0], target) message = pypSearch.ask_user_for_message_breakline_mode() await pypSearch.send_message(pages[0], message) message2 = pypSearch.ask_user_for_message() await pypSearch.send_message(pages[0], message2) ''' # endregion # region IMPORTS import asyncio import time from pathlib import Path from pyppeteer.errors import ElementHandleError from pyppeteer.browser import Browser from pyppeteer.page import Page from wplay.utils.helpers import whatsapp_selectors_dict, websites from wplay.utils.browser_config import load_website from wplay.utils.Logger import Logger from wplay.utils.helpers import logs_path from wplay import target_info # endregion # region LOGGER __logger = Logger(Path(__file__).name) # endregion # region FOR SCRIPTING async def search_and_select_target_all_ways(page: Page, target: str, hide_groups: bool = False): """ Function try to search with 'search_and_select_target' function, if any error occurs we try to search with 'search_and_select_target_without_new_chat_button' function. Arguments: page {Page} -- Pyppeteer page object target {str} -- string with target name or number hide_groups {bool} -- hide or not groups from search result (default: {False}) Returns: target_focused_title {str} -- Return the target focused title """ try: if not await search_target_by_number(page, target): await search_and_select_target(page, target) except Exception as e: __logger.error(f"Error searching target: {str(e)}. Trying the second way to search.") await page.reload() await search_and_select_target_without_new_chat_button(page, target) async def search_target_by_number(page: Page, target: str): if await __try_load_contact_by_number(page, target): # target_focused_title = await __get_focused_target_title(page, target) # await __display_complete_target_info(page,choosed_target,contact_tuple) await __wait_for_message_area(page) return True else: return False async def search_and_select_target(page: Page, target: str, hide_groups: bool = False): """ Function search for targets using the whatsapp new chat button. When this function print the list of target found it doesn't print the phone number, the phone is printed only after you choose the target. Arguments: page {Page} -- Pyppeteer page object target {str} -- string with target name or number hide_groups {bool} -- hide or not groups from search result (default: {False}) Returns: target_focused_title {str} -- Return the target focused title """ await __open_new_chat(page) await __type_in_new_chat_search_bar(page, target) contact_list_elements_unchecked = await __get_contacts_elements_filtered(page, target) group_list_elements_unchecked = await __get_groups_elements_filtered(page, target, hide_groups) contact_titles_unchecked = await __get_contacts_titles_from_elements_unchecked(page, contact_list_elements_unchecked) group_titles_unchecked = await __get_groups_titles_from_elements_unchecked(page, group_list_elements_unchecked) contact_list_unchecked = __zip_contact_titles_and_elements_unchecked(contact_titles_unchecked, contact_list_elements_unchecked) group_list_unchecked = __zip_group_titles_and_elements_unchecked(group_titles_unchecked, group_list_elements_unchecked) contact_tuple = __check_contact_list(target, contact_list_unchecked) group_tuple = __check_group_list(target, group_list_unchecked) target_tuple = __get_target_tuple(contact_tuple, group_tuple) __print_target_tuple(target_tuple) target_index_choosed = __ask_user_to_choose_the_filtered_target(target_tuple) choosed_target = __get_choosed_target(target_tuple, target_index_choosed) await __navigate_to_target(page, choosed_target) target_focused_title = await __get_focused_target_title(page, target) await __display_complete_target_info(page, choosed_target, contact_tuple) __check_target_focused_title(target, target_focused_title) await __wait_for_message_area(page) return target_focused_title async def search_and_select_target_without_new_chat_button(page: Page, target: str, hide_groups: bool = False): """ Function search for targets search bar in the whatsapp, without using the new chat button. Here we don't look for the contact by the number, we look for the number as if it were a string, just to be an alternative to the method used in the other function. When this function print the list of target found it print the phone number of every target, the phone is also printed after you choose the target. Arguments: page {Page} -- Pyppeteer page object target {str} -- string with target name or number hide_groups {bool} -- hide or not groups from search result (default: {False}) Returns: target_focused_title {str} -- Return the target focused title """ await __type_in_chat_or_message_search(page, target) chats_messages_groups_elements_list = await __get_chats_messages_groups_elements(page) contact_name_index_tuple_list = await __get_contacts_matched_with_query(chats_messages_groups_elements_list) group_name_index_tuple_list = await __get_groups_matched_with_query(chats_messages_groups_elements_list, hide_groups) await __get_number_of_filtered_contacts(page, contact_name_index_tuple_list, chats_messages_groups_elements_list) target_tuple = __get_target_tuple(contact_name_index_tuple_list, group_name_index_tuple_list) __print_target_tuple(target_tuple) target_index_chosen = __ask_user_to_choose_the_filtered_target(target_tuple) # chosen_target will be a tuple (a,b) such that a is the name of the target and b is the # index of that element in chats_messages_groups_elements_list chosen_target = __get_choosed_target(target_tuple, target_index_chosen) await __open_selected_chat(chosen_target[1], chats_messages_groups_elements_list) target_name = chosen_target[0] await __display_complete_target_info(page, chosen_target, contact_name_index_tuple_list) await __wait_for_message_area(page) return target_name # endregion # region SEARCH AND SELECT TARGET async def __accept_dialog(dialog): try: await dialog.accept() except: pass async def __try_load_contact_by_number(page: Page, target: str) -> bool: try: if int(target): __logger.debug("Loading contact by number.") page.on( 'dialog', lambda dialog: asyncio.ensure_future(__accept_dialog(dialog)) ) await load_website(page, f"{websites['wpp_unknown']}{target}") time.sleep(2) if (await page.evaluate(f'document.querySelector("{whatsapp_selectors_dict["invalid_number_ok_button"]}") != null')): await page.click(whatsapp_selectors_dict["invalid_number_ok_button"]) __logger.debug(f"Invalid number: {target}") print(f"Invalid Number: {target}") return False return True except Exception as e: __logger.error(f"Error loading contact by number: {str(e)}") return False return False async def __get_number_of_filtered_contacts(page: Page, contact_name_index_tuple_list: list, chats_messages_groups_elements_list: list): try: for index, contact_name_index_tuple in enumerate(contact_name_index_tuple_list): await chats_messages_groups_elements_list[contact_name_index_tuple[1]].click() await __open_target_chat_info_page(page) contact_page_elements = await __get_contact_page_elements(page) complete_target_info = {} await __get_contact_about_and_phone(contact_page_elements[3], complete_target_info) contact_name_index_tuple_list[index] = (contact_name_index_tuple[0] + " : " + complete_target_info['Mobile'], contact_name_index_tuple[1]) await __close_contact_info_page(page) except Exception as e: print(e) async def __display_complete_target_info(page, target_tuple, contact_tuple): complete_target_info = {} try: if any(target_tuple[0] in i for i in contact_tuple): complete_target_info = await __get_complete_info_on_target(page) __print_complete_target_info(complete_target_info) await __close_contact_info_page(page) else: complete_target_info = await __get_complete_info_on_group(page) __print_complete_target_info(complete_target_info) await __close_contact_info_page(page) except Exception as e: print(e) async def __type_in_chat_or_message_search(page, target): try: print(f'Looking for: {target}') await page.waitForSelector( whatsapp_selectors_dict['chat_or_message_search'], visible=True, timeout=0 ) await page.waitFor(500) await page.type(whatsapp_selectors_dict['chat_or_message_search'], target) await page.waitFor(3000) except Exception as e: print(e) async def __get_chats_messages_groups_elements(page: Page): chats_messages_groups_elements_list = [] # type : list[int] try: chats_messages_groups_elements_list = await page.querySelectorAll(whatsapp_selectors_dict['chats_groups_messages_elements']) return chats_messages_groups_elements_list except Exception as e: print(e) exit() async def __get_contacts_matched_with_query(chats_groups_messages_elements_list: list): contacts_to_choose_from = [] # type : list[str , int] get_contact_node_title_function = 'node => node.parentNode.getAttribute("title")' for idx, element in enumerate(chats_groups_messages_elements_list): try: contact_name = await element.querySelectorEval(whatsapp_selectors_dict['contact_element'], get_contact_node_title_function) if contact_name is not None: contacts_to_choose_from.append((contact_name, idx)) except ElementHandleError: # if it is not a contact element, move to the next one continue except Exception as e: print(e) return contacts_to_choose_from async def __get_groups_matched_with_query(chats_groups_messages_elements_list: list, hide_groups: bool): groups_to_choose_from = [] if hide_groups: return groups_to_choose_from get_group_node_title_function = 'node => node.parentNode.getAttribute("title")' for idx, element in enumerate(chats_groups_messages_elements_list): try: group_name = await element.querySelectorEval(whatsapp_selectors_dict['group_element'], get_group_node_title_function) groups_to_choose_from.append((group_name,idx)) except ElementHandleError: # if it is not a contact element, move to the next one continue except Exception as e: print(e) return groups_to_choose_from async def __open_selected_chat(target_index: int, chats_messages_groups_elements_list: list): try: await chats_messages_groups_elements_list[target_index].click() except Exception as e: print(f"This target doesn't exist! Error: {str(e)}") exit() async def __get_complete_info_on_target(page: Page): contact_page_elements = [] try: await __open_target_chat_info_page(page) contact_page_elements = await __get_contact_page_elements(page) complete_target_info = {} await __get_contact_name_info(contact_page_elements[0], complete_target_info) await __get_contact_about_and_phone(contact_page_elements[3], complete_target_info) await __get_contact_groups_common_with_target(complete_target_info, page) return complete_target_info except Exception as e: print(e) async def __get_complete_info_on_group(page): try: await __open_target_chat_info_page(page) contact_page_elements = await __get_contact_page_elements(page) complete_target_group_info = {} await __get_target_group_name(contact_page_elements[0],complete_target_group_info) await __get_target_group_creation_info(contact_page_elements[0],complete_target_group_info) await __get_target_group_description(contact_page_elements[1],complete_target_group_info) await __get_target_group_members(contact_page_elements[4],complete_target_group_info) return complete_target_group_info except Exception as e: print(e) async def __get_target_group_name(element, complete_target_info): try: get_inner_text_function = 'e => e.innerText' complete_target_info['Name'] = await element.querySelectorEval(whatsapp_selectors_dict['contact_info_page_target_group_name_element'],get_inner_text_function) except Exception as e: print(e) async def __get_target_group_creation_info(element, complete_target_info): try: get_inner_text_function = 'e => e.innerText' complete_target_info['Creation Info'] = await element.querySelectorEval\ (whatsapp_selectors_dict['contact_info_page_target_group_creation_info_element'],get_inner_text_function) except Exception as e: print(e) async def __get_target_group_description(element,complete_target_info): try: get_inner_text_function = 'e => e.innerText' complete_target_info['Description'] = await element.querySelectorEval \ (whatsapp_selectors_dict['contact_info_page_target_group_description_element'], get_inner_text_function) except Exception as e: print(e) async def __get_target_group_members(element, complete_target_info): try: children_elements = await element.querySelectorAll(':scope > div') if len(children_elements) <= 2: target_group_members_selector = ':scope > div:last-child > div > div' elif len(children_elements) == 3: target_group_members_selector = ':scope > div:nth-child(2) > div > div' else: target_group_members_selector = whatsapp_selectors_dict['contact_info_page_target_group_member_elements'] target_group_member_elements = await element.querySelectorAll(target_group_members_selector) group_member_name_selector = ':scope span[title]' get_element_title_function = 'e => e.getAttribute("title")' complete_target_info['Members'] = [await ele.querySelectorEval(group_member_name_selector,get_element_title_function) for ele in target_group_member_elements] except Exception as e: print(e) async def __open_target_chat_info_page(page): try: await page.waitForSelector( whatsapp_selectors_dict['target_chat_header'], visible=True, timeout=3000 ) await page.click(whatsapp_selectors_dict['target_chat_header']) except Exception as e: print(e) async def __get_contact_page_elements(page: Page): contact_page_elements = [] try: await page.waitForSelector( whatsapp_selectors_dict['contact_info_page_elements'], visible=True, timeout=8000 ) contact_page_elements = await page.querySelectorAll(whatsapp_selectors_dict['contact_info_page_elements']) return contact_page_elements except Exception as e: print(e) async def __get_contact_name_info(contact_name_element,complete_target_info): try: complete_target_info['Name'] = await contact_name_element.querySelectorEval('span > span', 'element => element.innerText') complete_target_info['Last_seen'] = await contact_name_element.querySelectorEval('div > span:last-of-type > div > span', 'element => element.getAttribute("title")') except: print(f'last seen not available') async def __get_contact_about_and_phone(contact_name_element, complete_target_info): try: complete_target_info['About'] = await contact_name_element.querySelectorEval('div:nth-child(2) > div > div > span > span', 'element => element.getAttribute("title")') complete_target_info['Mobile'] = await contact_name_element.querySelectorEval('div:last-of-type > div > div > span > span', 'element => element.innerText') target_info.target_contact_number(complete_target_info['Mobile']) except Exception as e: print(e) async def __get_contact_groups_common_with_target(complete_target_info,page): try: await page.waitForSelector( whatsapp_selectors_dict['contact_info_page_group_element_heading'], visible=True, timeout=3000 ) if (await page.evaluate(f'document.querySelector("{whatsapp_selectors_dict["contact_info_page_group_element_heading"]}").innerText'))\ == "Groups in common": group_elements = await page.querySelectorAll(whatsapp_selectors_dict['contact_info_page_group_elements']) complete_target_info['Groups'] = [await ele.querySelectorEval('div>div>div:nth-child(2)>div:first-child>div>div>span', 'e => e.getAttribute("title")') for ele in group_elements] else: complete_target_info['Groups'] = [] except: complete_target_info['Groups'] = [] print(f'No groups in common') async def __close_contact_info_page(page: Page): try: await page.waitForSelector( whatsapp_selectors_dict['contact_info_page_close_button'], visible=True, timeout=5000 ) await page.click(whatsapp_selectors_dict['contact_info_page_close_button']) except Exception as e: print(e) def __print_complete_target_info(complete_target_info): for key in complete_target_info.keys(): if key == "Groups" or key == "Members": print(key + ":") print(*complete_target_info[key], sep=",") else: print(f'{key}: {complete_target_info[key]} ') async def __open_new_chat(page: Page): await page.waitForSelector( whatsapp_selectors_dict['new_chat_button'], visible=True, timeout=0 ) await page.waitFor(500) await page.click(whatsapp_selectors_dict['new_chat_button']) async def __type_in_new_chat_search_bar(page: Page, target: str): print(f'Looking for: {target}') __logger.info('Searching Target') await page.waitForSelector( whatsapp_selectors_dict['search_contact_input_new_chat'], visible=True ) await page.type(whatsapp_selectors_dict['search_contact_input_new_chat'], target) await page.waitFor(3000) async def __get_contacts_elements_filtered(page: Page, target: str): contact_list_elements_unchecked = list() try: await page.waitForSelector( whatsapp_selectors_dict['contact_list_elements_filtered_new_chat'], visible=True, timeout=3000 ) contact_list_elements_unchecked = await page.querySelectorAll(whatsapp_selectors_dict['contact_list_elements_filtered_new_chat']) except: print(f'No contact named by "{target}"!') __logger.info('Target not found') return contact_list_elements_unchecked async def __get_groups_elements_filtered(page: Page, target: str, hide_groups: bool = False): group_list_elements_unchecked = list() if hide_groups: return group_list_elements_unchecked try: await page.waitForSelector( whatsapp_selectors_dict['group_list_elements_filtered_new_chat'], visible=True, timeout=3000 ) group_list_elements_unchecked = await page.querySelectorAll(whatsapp_selectors_dict['group_list_elements_filtered_new_chat']) except: print(f'No group named by "{target}"!') __logger.info('Target not found in groups') return group_list_elements_unchecked async def __get_contacts_titles_from_elements_unchecked(page: Page, contact_list_elements_unchecked: list): contact_titles_unchecked = [] for i in range(len(contact_list_elements_unchecked)): contact_titles_unchecked.append(await page.evaluate(f'document.querySelectorAll("{whatsapp_selectors_dict["contact_list_elements_filtered_new_chat"]}")[{i}].getAttribute("title")')) return contact_titles_unchecked async def __get_groups_titles_from_elements_unchecked(page: Page, group_list_elements_unchecked: list): group_titles_unchecked = [] for i in range(len(group_list_elements_unchecked)): group_titles_unchecked.append(await page.evaluate(f'document.querySelectorAll("{whatsapp_selectors_dict["group_list_elements_filtered_new_chat"]}")[{i}].getAttribute("title")')) return group_titles_unchecked # contact_list_unchecked is a zip (list of tuples) of contact_titles and # contact elements, unchecked. def __zip_contact_titles_and_elements_unchecked(contact_titles_unchecked, contact_list_elements_unchecked): contact_list_unchecked = list(zip(contact_titles_unchecked, contact_list_elements_unchecked)) return contact_list_unchecked def __zip_group_titles_and_elements_unchecked(group_titles_unchecked, group_list_elements_unchecked): group_list_unchecked = list(zip(group_titles_unchecked, group_list_elements_unchecked)) return group_list_unchecked # __checking_contact_list verify if target is in title, if not we pop from list def __check_contact_list(target: str, contact_list_unchecked): i = 0 while i < len(contact_list_unchecked): if len(contact_list_unchecked) <= 0: break # we can add more verifications if we are getting false-positive contacts if contact_list_unchecked[i][0].lower().find(target.lower()) == -1: try: contact_list_unchecked.pop(i) except Exception as e: print(f'Error: {str(e)}') i -= 1 i += 1 contact_tuple = tuple(contact_list_unchecked) return contact_tuple def __check_group_list(target: str, group_list_unchecked): i = 0 while i < len(group_list_unchecked): if len(group_list_unchecked) <= 0: break # we can add more verifications if we are getting false-positive groups if group_list_unchecked[i][0].lower().find(target.lower()) == -1: try: group_list_unchecked.pop(i) except Exception as e: print(f'Error: {str(e)}') i -= 1 i += 1 group_tuple = tuple(group_list_unchecked) return group_tuple # target_list is like that: (((0, 'a'), (1, 'b')), ((3, 'c'), (4, 'd'))), # but instead numbers and letters we have titles and elements # the first index is the contacts and the second is the groups def __get_target_tuple(contact_tuple, group_tuple): target_tuple = (contact_tuple, group_tuple) # check to see if the target exits in the user's address book if len(target_tuple[0]) == 0 and len(target_tuple[1]) == 0: print('The target does not exist, please enter a valid target name') __logger.error('Invalid target name entered') exit() return target_tuple def __print_target_tuple(target_tuple): lenght_of_contacts_tuple = len(target_tuple[0]) lenght_of_groups_tuple = len(target_tuple[1]) for i in range(lenght_of_contacts_tuple): if lenght_of_contacts_tuple <= 0: break if i == 0: print("Contacts found:") __logger.info('List of Targets') print(f'{i}: {target_tuple[0][i][0]}') for i in range(lenght_of_contacts_tuple, lenght_of_groups_tuple + lenght_of_contacts_tuple): if lenght_of_groups_tuple <= 0: break if i == lenght_of_contacts_tuple: print("Groups found:") __logger.info('List of Target in groups') print(f'{i}: {target_tuple[1][i-lenght_of_contacts_tuple][0]}') def __ask_user_to_choose_the_filtered_target(target_tuple): if len(target_tuple[0] + target_tuple[1]) > 0: __logger.info('Input Target Number') target_index_choosed = int( input('Enter the number of the target you wish to choose: ')) return target_index_choosed def __get_choosed_target(target_tuple, target_index_choosed): lenght_of_contacts_tuple = len(target_tuple[0]) if target_index_choosed is None: exit() try: if target_index_choosed < lenght_of_contacts_tuple: choosed_target = target_tuple[0][target_index_choosed] elif target_index_choosed >= lenght_of_contacts_tuple: choosed_target = target_tuple[1][target_index_choosed - lenght_of_contacts_tuple] else: print("This target doesn't exist!") __logger.error('Invalid Target') exit() except Exception as e: print(f"This target doesn't exist! Error: {str(e)}") __logger.error('Invalid Target') exit() return choosed_target async def __navigate_to_target(page: Page, choosed_target): try: await choosed_target[1].click() except Exception as e: print(f"This target doesn't exist! Error: {str(e)}") __logger.error('Invalid Target') exit() async def __get_focused_target_title(page: Page, target): try: await page.waitForSelector(whatsapp_selectors_dict['target_focused_title']) target_focused_title = await page.evaluate(f'document.querySelector("{whatsapp_selectors_dict["target_focused_title"]}").getAttribute("title")') except Exception as e: print(f'No target selected! Error: {str(e)}') __logger.error('Target not selected from list') exit() return target_focused_title def __print_selected_target_title(target_focused_title): print(f"You've selected the target named by: {target_focused_title}") __logger.info('Selected Target') def __check_target_focused_title(target, target_focused_title): """if int(target): def only_numerics(seq): seq_type= type(seq) return seq_type().join(filter(seq_type.isdigit, seq)) target = only_numerics(target) target_focused_title = only_numerics(target_focused_title) if target_focused_title.strip().find(target) == -1: print(f"Maybe you're focused in the wrong target, {target_focused_title}") must_continue = str(input("Do you want to continue (yes/no)? ")) accepted_yes = {'yes', 'y'} if not must_continue.lower() in accepted_yes: exit() """ if target_focused_title.lower().find(target.lower()) == -1: print(f"You're focused in the wrong target, {target_focused_title}") must_continue = str(input("Do you want to continue (yes/no)? ")) accepted_yes = {'yes', 'y'} if not must_continue.lower() in accepted_yes: exit() async def __wait_for_message_area(page: Page): try: await page.waitForSelector(whatsapp_selectors_dict['message_area']) except Exception as e: print(f"You don't belong this group anymore! Error: {str(e)}") # endregion ================================================ FILE: wplay/utils/target_select.py ================================================ # region IMPORTS from pathlib import Path from pyppeteer.page import Page from wplay.utils import target_search from wplay.utils.Logger import Logger from wplay.utils.helpers import whatsapp_selectors_dict # endregion # region FOR SCRIPTING async def manual_select_target(page: Page, hide_groups: bool = False): __print_manual_selection_info() await __open_new_chat(page) target_focused_title = await __get_focused_target_title(page) await __wait_for_message_area(page) __print_selected_target_title(target_focused_title) complete_target_info = await target_search.__get_complete_info_on_target(page) target_search.__print_complete_target_info(complete_target_info) await __close_contact_info_page(page) return target_focused_title # endregion # region SELECT TARGET def __print_manual_selection_info(): print(f"You've to go to whatsapp web and select target manually") def __print_selected_target_title(target_focused_title: str): print(f"You've selected the target named by: {target_focused_title}") async def __close_contact_info_page(page: Page): try: await page.waitForSelector( whatsapp_selectors_dict['contact_info_page_close_button'], visible=True, timeout=5000 ) await page.click(whatsapp_selectors_dict['contact_info_page_close_button']) except Exception as e: print(e) async def __open_new_chat(page: Page): await page.waitForSelector( whatsapp_selectors_dict['new_chat_button'], visible=True, timeout=0 ) async def __get_focused_target_title(page: Page): try: await page.waitForSelector(whatsapp_selectors_dict['target_focused_title'], visible=True, timeout=0) target_focused_title = await page.evaluate(f'document.querySelector("{whatsapp_selectors_dict["target_focused_title"]}").getAttribute("title")') except Exception as e: print(f'No target selected! Error: {str(e)}') exit() return target_focused_title async def __wait_for_message_area(page: Page): try: await page.waitForSelector(whatsapp_selectors_dict['message_area'], timeout=0) except Exception as e: print(f"You don't belong this group anymore! Error: {str(e)}") # endregion ================================================ FILE: wplay/utils/verify_internet.py ================================================ import http.client as httplib def internet_avalaible(): """ Checks internet connection. """ conn = httplib.HTTPConnection("www.google.com", timeout=5) try: conn.request("HEAD", "/") conn.close() return True except: conn.close() return False