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.<br />
We have a code of conduct, please follow it in all your interactions with the project.<br />
# 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. <br />
2.Update the README.md with details of changes including environment variables, various file parameters and container details **if needed**.<br />
3.Try adding screenshots in your pull request of the changes you made. <br />
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 <kbd><b>Fork</b></kbd></a> button.
[](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
================================================
<div align="center">
<img src="images/logo.png">
# whatsapp-play
[](https://pepy.tech/project/wplay)
[](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)
[](https://pypi.org/project/wplay/)

[](https://hub.docker.com/repository/docker/rpotter12/whatsapp-play/general)
[](https://travis-ci.org/rpotter12/whatsapp-play)
[](https://codecov.io/gh/rpotter12/whatsapp-play)
[](https://twitter.com/rpotter121998)
[](http://hits.dwyl.io/rpotter12/whatsapp-play)
[](https://gitter.im/whatsapp-play/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
[](https://gitpod.io/#https://github.com/rpotter12/whatsapp-play)
</div>
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 <https://newsapi.org/> 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: <br />
Windows: `python -m pip install wplay` <br />
Unix/Linux/Mac: `python3 -m pip install wplay` <br />
**Installation Video:** [Simple Installation Link](https://youtu.be/HS6ksu6rCxQ)
### Alternate way - Run whatsapp-play from source code: <br />
**Windows**<br />
```
$ 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**<br />
```
$ git clone https://github.com/rpotter12/whatsapp-play.git
$ cd whatsapp-play
$ python3 -m pip install -r requirements.txt
$ python3 -m wplay -h
```
## Usage
<img src="images/usage.png"><br />
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. <br />
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. <br />
Steps to create a launcher with arguments follow the steps bellow: <br />
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)<br />
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
[](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 <sender> -r <receiver> ')
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
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
SYMBOL INDEX (151 symbols across 33 files)
FILE: tests/test_func.py
function _get_py_exe (line 43) | def _get_py_exe():
function get_test_subprocess (line 74) | def get_test_subprocess(cmd=None, **kwds):
function wait_for_pid (line 109) | def wait_for_pid(pid):
function wait_for_file (line 119) | def wait_for_file(fname, delete=True, empty=False):
function safe_rmpath (line 130) | def safe_rmpath(path):
FILE: tests/test_helpers.py
class testPath (line 14) | class testPath(unittest.TestCase):
method test_paths_exist (line 16) | def test_paths_exist(self):
FILE: tests/test_kill_child_process.py
class Testkill (line 15) | class Testkill(unittest.TestCase):
method test_kill (line 17) | def test_kill(self):
FILE: tests/test_logger.py
class CaptureLogsExample (line 19) | class CaptureLogsExample(unittest.TestCase):
method test_assert_logs (line 21) | def test_assert_logs(self):
FILE: wplay/__main__.py
function print_logo (line 32) | def print_logo(text_logo):
function get_arg_parser (line 38) | def get_arg_parser():
function get_and_match_args (line 153) | async def get_and_match_args(parser):
function main (line 204) | async def main():
FILE: wplay/about_changer.py
function about_changer (line 17) | async def about_changer():
function change_about (line 26) | async def change_about():
function get_api_key (line 41) | async def get_api_key():
function about_changer_news (line 49) | async def about_changer_news():
function fetch_news (line 77) | def fetch_news(query):
FILE: wplay/broadcast_message.py
class InvalidNumber (line 19) | class InvalidNumber(Exception):
function ProcessNumbers (line 23) | def ProcessNumbers():
function broadcast (line 40) | async def broadcast():
FILE: wplay/chat_intermediator.py
function intermediary (line 13) | async def intermediary(sender, receiver):
FILE: wplay/chatbot.py
function Bot (line 6) | async def Bot(last_Message):
function say_hi (line 58) | def say_hi():
function say_goodmorning (line 63) | def say_goodmorning():
function say_goodnight (line 68) | def say_goodnight():
function say_fine (line 73) | def say_fine():
function _help_commands (line 78) | def _help_commands():
function takeScreenshot (line 92) | async def takeScreenshot(qry):
FILE: wplay/download_media.py
function download_media (line 19) | async def download_media(target):
FILE: wplay/get_media.py
function get_profile_photos (line 16) | async def get_profile_photos():
FILE: wplay/get_news.py
function get_news (line 24) | async def get_news(target):
FILE: wplay/message_blast.py
function message_blast (line 19) | async def message_blast(target: str):
FILE: wplay/message_service.py
function message_service (line 45) | async def message_service():
FILE: wplay/message_timer.py
function message_timer (line 20) | async def message_timer(target):
FILE: wplay/online_tracker.py
function tracker (line 19) | async def tracker(target):
FILE: wplay/profile_download.py
function get_profile_picture (line 18) | async def get_profile_picture(target):
FILE: wplay/save_chat.py
function save_chat (line 17) | async def save_chat(target):
FILE: wplay/schedule_message.py
function schedule_message (line 20) | async def schedule_message(target):
FILE: wplay/target_info.py
function formatNumber (line 21) | def formatNumber(InputNumber):
function localScan (line 25) | def localScan(InputNumber, print_results=True):
function scanNumber (line 77) | def scanNumber(InputNumber):
function target_contact_number (line 88) | def target_contact_number(num):
function target_info (line 92) | async def target_info(target):
FILE: wplay/telegram_bot.py
function start_tkinter (line 21) | def start_tkinter():
function ask_where_are_the_status_file (line 26) | def ask_where_are_the_status_file():
function startmessage (line 39) | def startmessage(bot, update):
function send_status (line 47) | def send_status(bot, update):
function telegram_status (line 60) | def telegram_status(name):
FILE: wplay/terminal_chat.py
function chat (line 22) | async def chat(target):
function getMessages (line 92) | async def getMessages(page, target):
FILE: wplay/text_to_speech.py
function text_to_speech (line 16) | async def text_to_speech(target):
FILE: wplay/utils/Logger.py
class Logger (line 8) | class Logger:
method __init__ (line 17) | def __init__(self, script_name: str, level: int = logging.WARNING):
method debug (line 39) | def debug(self, msg: str):
method error (line 42) | def error(self, msg: str):
method info (line 45) | def info(self, msg: str):
FILE: wplay/utils/MessageStack.py
class MessageStack (line 11) | class MessageStack():
method __init__ (line 12) | def __init__(self):
method __create_json_file (line 19) | def __create_json_file(self, file_path: Path):
method __write_json (line 24) | def __write_json(self, data: dict, file_path: Path):
method __ensure_valid_json (line 28) | def __ensure_valid_json(self, file_path: Path):
method append_message (line 39) | def append_message(self, message: dict, file_path: Path):
method get_message (line 56) | def get_message(
method get_all_messages (line 76) | def get_all_messages(
method move_message (line 84) | def move_message(self, from_file_path: Path, to_file_path: Path, uuid:...
method move_all_messages (line 92) | def move_all_messages(self, from_file_path: Path, to_file_path: Path):
method remove_message (line 100) | def remove_message(self, uuid: str, file_path: Path):
method remove_all_messages (line 109) | def remove_all_messages(self, file_path: Path):
FILE: wplay/utils/SessionManager.py
class SessionManager (line 30) | class SessionManager(object):
method __init__ (line 31) | def __init__(self):
method reset_fields (line 46) | def reset_fields(self):
method get_user_data_filenames (line 54) | def get_user_data_filenames(self):
method prepare_questions (line 57) | def prepare_questions(self):
method get_answer_menu (line 105) | def get_answer_menu(self):
method verify_answers (line 108) | def verify_answers(self):
method __verify_if_session_file_exists (line 139) | def __verify_if_session_file_exists(self) -> bool:
method __delete_session_data (line 147) | def __delete_session_data(self, path):
method session_manager (line 157) | def session_manager():
FILE: wplay/utils/browser_config.py
function configure_browser_and_load_whatsapp (line 49) | async def configure_browser_and_load_whatsapp() -> (Page, Browser):
function get_pages (line 67) | async def get_pages(browser: Browser) -> List[Page]:
function open_new_page (line 72) | async def open_new_page(browser: Browser):
function config_page (line 80) | async def config_page(page: Page):
function load_website (line 86) | async def load_website(page: Page, website: str):
function exit_if_wrong_url (line 92) | def exit_if_wrong_url(page: Page, browser: Browser, url_to_check: str):
function __patch_pyppeteer (line 104) | def __patch_pyppeteer():
function __config_browser (line 124) | async def __config_browser(username: str = None, save_session: bool = Fa...
function __set_user_agent (line 132) | async def __set_user_agent(page: Page):
function __set_view_port (line 136) | async def __set_view_port(page: Page):
FILE: wplay/utils/helpers.py
function create_dirs (line 90) | def create_dirs():
function kill_child_processes (line 104) | def kill_child_processes(parent_pid, sig=signal.SIGTERM):
FILE: wplay/utils/io.py
function ask_user_for_message (line 19) | def ask_user_for_message() -> str:
function ask_user_for_message_breakline_mode (line 23) | def ask_user_for_message_breakline_mode() -> List[str]:
function send_message (line 38) | async def send_message(page: Page, message: Union[List[str], str]):
function send_file (line 49) | async def send_file(page):
FILE: wplay/utils/target_data.py
function get_last_seen_from_focused_target (line 18) | async def get_last_seen_from_focused_target(page: Page):
FILE: wplay/utils/target_search.py
function search_and_select_target_all_ways (line 45) | async def search_and_select_target_all_ways(page: Page, target: str, hid...
function search_target_by_number (line 67) | async def search_target_by_number(page: Page, target: str):
function search_and_select_target (line 77) | async def search_and_select_target(page: Page, target: str, hide_groups:...
function search_and_select_target_without_new_chat_button (line 114) | async def search_and_select_target_without_new_chat_button(page: Page, t...
function __accept_dialog (line 152) | async def __accept_dialog(dialog):
function __try_load_contact_by_number (line 159) | async def __try_load_contact_by_number(page: Page, target: str) -> bool:
function __get_number_of_filtered_contacts (line 182) | async def __get_number_of_filtered_contacts(page: Page, contact_name_ind...
function __display_complete_target_info (line 196) | async def __display_complete_target_info(page, target_tuple, contact_tup...
function __type_in_chat_or_message_search (line 211) | async def __type_in_chat_or_message_search(page, target):
function __get_chats_messages_groups_elements (line 226) | async def __get_chats_messages_groups_elements(page: Page):
function __get_contacts_matched_with_query (line 236) | async def __get_contacts_matched_with_query(chats_groups_messages_elemen...
function __get_groups_matched_with_query (line 253) | async def __get_groups_matched_with_query(chats_groups_messages_elements...
function __open_selected_chat (line 274) | async def __open_selected_chat(target_index: int, chats_messages_groups_...
function __get_complete_info_on_target (line 282) | async def __get_complete_info_on_target(page: Page):
function __get_complete_info_on_group (line 296) | async def __get_complete_info_on_group(page):
function __get_target_group_name (line 310) | async def __get_target_group_name(element, complete_target_info):
function __get_target_group_creation_info (line 318) | async def __get_target_group_creation_info(element, complete_target_info):
function __get_target_group_description (line 327) | async def __get_target_group_description(element,complete_target_info):
function __get_target_group_members (line 336) | async def __get_target_group_members(element, complete_target_info):
function __open_target_chat_info_page (line 356) | async def __open_target_chat_info_page(page):
function __get_contact_page_elements (line 368) | async def __get_contact_page_elements(page: Page):
function __get_contact_name_info (line 382) | async def __get_contact_name_info(contact_name_element,complete_target_i...
function __get_contact_about_and_phone (line 390) | async def __get_contact_about_and_phone(contact_name_element, complete_t...
function __get_contact_groups_common_with_target (line 399) | async def __get_contact_groups_common_with_target(complete_target_info,p...
function __close_contact_info_page (line 418) | async def __close_contact_info_page(page: Page):
function __print_complete_target_info (line 430) | def __print_complete_target_info(complete_target_info):
function __open_new_chat (line 439) | async def __open_new_chat(page: Page):
function __type_in_new_chat_search_bar (line 449) | async def __type_in_new_chat_search_bar(page: Page, target: str):
function __get_contacts_elements_filtered (line 460) | async def __get_contacts_elements_filtered(page: Page, target: str):
function __get_groups_elements_filtered (line 476) | async def __get_groups_elements_filtered(page: Page, target: str, hide_g...
function __get_contacts_titles_from_elements_unchecked (line 496) | async def __get_contacts_titles_from_elements_unchecked(page: Page, cont...
function __get_groups_titles_from_elements_unchecked (line 503) | async def __get_groups_titles_from_elements_unchecked(page: Page, group_...
function __zip_contact_titles_and_elements_unchecked (line 512) | def __zip_contact_titles_and_elements_unchecked(contact_titles_unchecked...
function __zip_group_titles_and_elements_unchecked (line 517) | def __zip_group_titles_and_elements_unchecked(group_titles_unchecked, gr...
function __check_contact_list (line 523) | def __check_contact_list(target: str, contact_list_unchecked):
function __check_group_list (line 542) | def __check_group_list(target: str, group_list_unchecked):
function __get_target_tuple (line 564) | def __get_target_tuple(contact_tuple, group_tuple):
function __print_target_tuple (line 574) | def __print_target_tuple(target_tuple):
function __ask_user_to_choose_the_filtered_target (line 595) | def __ask_user_to_choose_the_filtered_target(target_tuple):
function __get_choosed_target (line 603) | def __get_choosed_target(target_tuple, target_index_choosed):
function __navigate_to_target (line 624) | async def __navigate_to_target(page: Page, choosed_target):
function __get_focused_target_title (line 633) | async def __get_focused_target_title(page: Page, target):
function __print_selected_target_title (line 644) | def __print_selected_target_title(target_focused_title):
function __check_target_focused_title (line 649) | def __check_target_focused_title(target, target_focused_title):
function __wait_for_message_area (line 673) | async def __wait_for_message_area(page: Page):
FILE: wplay/utils/target_select.py
function manual_select_target (line 13) | async def manual_select_target(page: Page, hide_groups: bool = False):
function __print_manual_selection_info (line 27) | def __print_manual_selection_info():
function __print_selected_target_title (line 31) | def __print_selected_target_title(target_focused_title: str):
function __close_contact_info_page (line 35) | async def __close_contact_info_page(page: Page):
function __open_new_chat (line 47) | async def __open_new_chat(page: Page):
function __get_focused_target_title (line 55) | async def __get_focused_target_title(page: Page):
function __wait_for_message_area (line 65) | async def __wait_for_message_area(page: Page):
FILE: wplay/utils/verify_internet.py
function internet_avalaible (line 4) | def internet_avalaible():
Condensed preview — 59 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (151K chars).
[
{
"path": ".circleci/config.yml",
"chars": 1326,
"preview": "# Python CircleCI 2.0 configuration file\n#\n# Check https://circleci.com/docs/2.0/language-python/ for more details\n#\nver"
},
{
"path": ".github/FUNDING.yml",
"chars": 815,
"preview": "# These are supported funding model platforms\n# repo: rpotter12/whatsapp-play\n# filename: FUNDING.YML\n\ngithub: [rpot"
},
{
"path": ".github/ISSUE_TEMPLATE/bug_report.md",
"chars": 834,
"preview": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Describe the b"
},
{
"path": ".github/ISSUE_TEMPLATE/feature_request.md",
"chars": 595,
"preview": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Is your fea"
},
{
"path": ".github/IssueTemplate/BugReportTemplate.md",
"chars": 633,
"preview": "---\r\nname: Bug report\r\nabout: Create a report to help us improve\r\ntitle: ''\r\nlabels: ''\r\nassignees: ''\r\n\r\n---\r\n\r\n**Descr"
},
{
"path": ".github/IssueTemplate/FeatureRequestTemplate.md",
"chars": 576,
"preview": "---\r\nname: Feature request\r\nabout: Suggest an idea for this project\r\ntitle: ''\r\nlabels: ''\r\nassignees: ''\r\n\r\n---\r\n\r\n**Is"
},
{
"path": ".github/Pull_Request_Template.md",
"chars": 1035,
"preview": "## Issue that this pull request solves\n\nCloses: # (issue number)\n\n## Proposed changes\n\nBrief description of what is fixe"
},
{
"path": ".github/workflows/pylint.yml",
"chars": 1115,
"preview": "name: Pylint\n\non: [push]\n\njobs:\n build:\n\n runs-on: ubuntu-latest\n\n steps:\n - uses: actions/checkout@v2\n - n"
},
{
"path": ".github/workflows/python-app.yml",
"chars": 1183,
"preview": "# This workflow will install Python dependencies, run tests and lint with a single version of Python\n# For more informat"
},
{
"path": ".gitignore",
"chars": 2220,
"preview": "# Personal Data\n.userData\ntracking_data\ndata\ntelegram_token.pkl\n\n# Byte-compiled / optimized / DLL files\nwplay.egg-info/"
},
{
"path": ".gitpod/.gitpod.Dockerfile",
"chars": 363,
"preview": "FROM custom\n \nUSER gitpod\n\n# Install custom tools, runtime, etc. using apt-get\n# For example, the com"
},
{
"path": ".gitpod/.gitpod.yml",
"chars": 85,
"preview": "tasks:\n - init: pip install -r ./requirements.txt\nimage:\n file: .gitpod.Dockerfile\n"
},
{
"path": ".travis.yml",
"chars": 403,
"preview": "language: python\n\n#sudo: false\n\npython:\n - 3.6\n - 3.7\n - 3.8\n - pypy3\n\ninstall:\n - python -m pip install -U pip\n -"
},
{
"path": "CODE_OF_CONDUCT.md",
"chars": 3355,
"preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, w"
},
{
"path": "CONTRIBUTING.md",
"chars": 5511,
"preview": "# Contributing guidelines\n\nTo start contributing to [whatsapp-play](https://github.com/rpotter12/whatsapp-play) project,"
},
{
"path": "Dockerfile",
"chars": 729,
"preview": "#To build docker image from this file run\n#docker build .\n#on terminal\n\nFROM python:3.6-alpine\n#LABEL MAINTAINER\n\n# Copy"
},
{
"path": "LICENSE",
"chars": 1069,
"preview": "MIT License\n\nCopyright (c) 2019 Rohit Potter\n\nPermission is hereby granted, free of charge, to any person obtaining a co"
},
{
"path": "README.md",
"chars": 4615,
"preview": "<div align=\"center\">\n <img src=\"images/logo.png\">\n \n# whatsapp-play\n\n[ as f:\n long_description = f.read()\n\nsetup(\n "
},
{
"path": "tests/test_func.py",
"chars": 4424,
"preview": "#region imports\nimport functools\nimport os\nimport shutil\nimport stat\nimport subprocess\nimport sys\nimport time\n\nimport ps"
},
{
"path": "tests/test_helpers.py",
"chars": 1248,
"preview": "'''\nTo run tests run\npython3 -m unittest discover -s tests\non terminal\n'''\n#region imports\nimport unittest\nfrom os impor"
},
{
"path": "tests/test_kill_child_process.py",
"chars": 598,
"preview": "'''\nTo run tests run\npython3 -m unittest discover -s tests\non terminal\n'''\n#region imports\nimport unittest\nimport psutil"
},
{
"path": "tests/test_logger.py",
"chars": 604,
"preview": "'''\nTo run tests run\npython3 -m unittest discover -s tests\non terminal\n'''\n#region imports\nimport unittest\nfrom wplay.ut"
},
{
"path": "wplay/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "wplay/__main__.py",
"chars": 5815,
"preview": "# region IMPORTS\nimport argparse\nimport asyncio\nimport sys\nimport os\nfrom pathlib import Path\n\nfrom pyfiglet import Figl"
},
{
"path": "wplay/about_changer.py",
"chars": 2961,
"preview": "# region IMPORTS\nimport time\nfrom pathlib import Path\nfrom wplay.utils.helpers import whatsapp_selectors_dict\nfrom wplay"
},
{
"path": "wplay/broadcast_message.py",
"chars": 1600,
"preview": "# region IMPORTS\nfrom tkinter import Tk\nfrom tkinter.filedialog import askopenfile\nfrom pathlib import Path\n\nfrom wplay."
},
{
"path": "wplay/chat_intermediator.py",
"chars": 423,
"preview": "# region IMPORTS\nfrom wplay import terminal_chat\nfrom wplay.utils.Logger import Logger\n\nfrom pathlib import Path\n# endre"
},
{
"path": "wplay/chatbot.py",
"chars": 3327,
"preview": "from googlesearch import search\nfrom pyppeteer import launch\nfrom wplay.utils.helpers import chatbot_image_folder_path\n\n"
},
{
"path": "wplay/download_media.py",
"chars": 4303,
"preview": "# region Imports\nfrom pathlib import Path\n\nfrom wplay.utils import browser_config\nfrom wplay.utils import target_search\n"
},
{
"path": "wplay/get_media.py",
"chars": 1800,
"preview": "# region Imports\nimport time\nfrom pathlib import Path\n\nfrom wplay.utils import browser_config\nfrom wplay.utils.Logger im"
},
{
"path": "wplay/get_news.py",
"chars": 1652,
"preview": "# region IMPORTS\nfrom pathlib import Path\nimport time\n\nfrom newsapi.newsapi_client import NewsApiClient\n\nfrom wplay.util"
},
{
"path": "wplay/message_blast.py",
"chars": 995,
"preview": "# region IMPORTS\nfrom pathlib import Path\nfrom typing import List\n\nfrom wplay.utils import browser_config\nfrom wplay.uti"
},
{
"path": "wplay/message_service.py",
"chars": 3396,
"preview": "# region IMPORTS\nfrom pathlib import Path\nimport threading\nimport time\nimport json\n\nfrom wplay.utils import browser_conf"
},
{
"path": "wplay/message_timer.py",
"chars": 1877,
"preview": "# region IMPORTS\nimport time\nimport random\nfrom pathlib import Path\n\nfrom wplay.utils import browser_config\nfrom wplay.u"
},
{
"path": "wplay/online_tracker.py",
"chars": 3151,
"preview": "import time\nfrom pathlib import Path\nfrom datetime import datetime\nfrom playsound import playsound\nfrom wplay.utils impo"
},
{
"path": "wplay/profile_download.py",
"chars": 1343,
"preview": "# region IMPORTS\nfrom pathlib import Path\n\nfrom wplay.utils import browser_config\nfrom wplay.utils import target_search\n"
},
{
"path": "wplay/save_chat.py",
"chars": 2159,
"preview": "# region IMPORTS\nfrom pathlib import Path\n\nfrom wplay.utils import browser_config\nfrom wplay.utils import target_search\n"
},
{
"path": "wplay/schedule_message.py",
"chars": 1379,
"preview": "# region IMPORTS\nfrom datetime import datetime\nfrom pathlib import Path\nimport time\nimport sys\n\nfrom wplay.utils import "
},
{
"path": "wplay/settings.cfg",
"chars": 249,
"preview": "[auth]\ngmail = alias@gmail.com\npassw = yourpassword\ndevid = 0000000000000000\n\n[app]\npkg = com.whatsapp\nsig = 38a0f7d505f"
},
{
"path": "wplay/target_info.py",
"chars": 4072,
"preview": "# region IMPORTS\nfrom wplay.utils import browser_config\nfrom wplay.utils import target_search\nfrom wplay.utils import ta"
},
{
"path": "wplay/telegram_bot.py",
"chars": 2549,
"preview": "# region IMPORTS\nimport tkinter\nfrom tkinter import filedialog\nfrom pathlib import Path\nimport pickle\n\nfrom telegram.ext"
},
{
"path": "wplay/terminal_chat.py",
"chars": 5120,
"preview": "# region IMPORTS\nfrom pathlib import Path\n\nfrom wplay.utils import browser_config\nfrom wplay.utils import target_search\n"
},
{
"path": "wplay/text_to_speech.py",
"chars": 1433,
"preview": "# region IMPORTS\nfrom pathlib import Path\n\nfrom wplay.utils.helpers import audio_file_folder_path\nfrom wplay.utils.Logge"
},
{
"path": "wplay/utils/Logger.py",
"chars": 1517,
"preview": "# region IMPORTS\nimport logging\nfrom pathlib import Path\nfrom wplay.utils.helpers import log_file_path, logs_path, test_"
},
{
"path": "wplay/utils/MessageStack.py",
"chars": 4423,
"preview": "# region IMPORTS\nfrom pathlib import Path\nfrom typing import List, Iterator\nimport json\n\nfrom wplay.utils import helpers"
},
{
"path": "wplay/utils/SessionManager.py",
"chars": 6727,
"preview": "# region Imports\nimport os\nimport stat\nimport shutil\nfrom pathlib import Path\n\nfrom whaaaaat import Separator, prompt\nfr"
},
{
"path": "wplay/utils/TODO",
"chars": 504,
"preview": "#TODO all code: return browser.close and use it instead use exit\n\n#TODO target_search: Add a whaaaaaat menu\n#FIXME targe"
},
{
"path": "wplay/utils/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "wplay/utils/browser_config.py",
"chars": 4604,
"preview": "# region TUTORIAL\n'''\nGo to region 'FOR SCRIPTING' and use the methods in your script!\n\nEXAMPLE OF USAGE:\nfrom wplay.pyp"
},
{
"path": "wplay/utils/helpers.py",
"chars": 6413,
"preview": "# region IMPORTS\nfrom pathlib import Path\n\nimport signal\nimport psutil\nfrom whaaaaat import style_from_dict, Token\n# end"
},
{
"path": "wplay/utils/io.py",
"chars": 1659,
"preview": "# region IMPORTS\nfrom pathlib import Path\nfrom typing import List, Union\n\nfrom pyppeteer.page import Page\n\nfrom wplay.ut"
},
{
"path": "wplay/utils/target_data.py",
"chars": 739,
"preview": "# region IMPORTS\nfrom pathlib import Path\n\nfrom pyppeteer.page import Page\n\nfrom wplay.utils.helpers import whatsapp_sel"
},
{
"path": "wplay/utils/target_search.py",
"chars": 27846,
"preview": "# region TUTORIAL\n'''\nGo to region 'FOR SCRIPTING' and use the methods in your script!\n\nEXAMPLE OF USAGE:\nfrom wplay.pyp"
},
{
"path": "wplay/utils/target_select.py",
"chars": 2288,
"preview": "# region IMPORTS\nfrom pathlib import Path\n\nfrom pyppeteer.page import Page\n\nfrom wplay.utils import target_search\nfrom w"
},
{
"path": "wplay/utils/verify_internet.py",
"chars": 307,
"preview": "import http.client as httplib\n\n\ndef internet_avalaible():\n \"\"\"\n Checks internet connection.\n \"\"\"\n conn = htt"
}
]
About this extraction
This page contains the full source code of the whatsplay/whatsapp-play GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 59 files (139.7 KB), approximately 33.8k tokens, and a symbol index with 151 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.