├── wplay ├── __init__.py ├── utils │ ├── __init__.py │ ├── verify_internet.py │ ├── TODO │ ├── target_data.py │ ├── Logger.py │ ├── io.py │ ├── target_select.py │ ├── MessageStack.py │ ├── browser_config.py │ ├── helpers.py │ ├── SessionManager.py │ └── target_search.py ├── plucky.wav ├── settings.cfg ├── chat_intermediator.py ├── message_blast.py ├── profile_download.py ├── schedule_message.py ├── text_to_speech.py ├── broadcast_message.py ├── get_news.py ├── get_media.py ├── message_timer.py ├── save_chat.py ├── telegram_bot.py ├── about_changer.py ├── online_tracker.py ├── chatbot.py ├── message_service.py ├── target_info.py ├── download_media.py ├── terminal_chat.py └── __main__.py ├── images ├── logo.png ├── usage.png └── infographic.png ├── .gitpod ├── .gitpod.yml └── .gitpod.Dockerfile ├── .travis.yml ├── requirements.txt ├── .github ├── IssueTemplate │ ├── FeatureRequestTemplate.md │ └── BugReportTemplate.md ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── FUNDING.yml ├── Pull_Request_Template.md └── workflows │ ├── pylint.yml │ └── python-app.yml ├── tests ├── test_logger.py ├── test_kill_child_process.py ├── test_helpers.py └── test_func.py ├── Dockerfile ├── LICENSE ├── .circleci └── config.yml ├── setup.py ├── .gitignore ├── CODE_OF_CONDUCT.md ├── README.md └── CONTRIBUTING.md /wplay/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wplay/utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rpotter12/whatsapp-play/HEAD/images/logo.png -------------------------------------------------------------------------------- /images/usage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rpotter12/whatsapp-play/HEAD/images/usage.png -------------------------------------------------------------------------------- /wplay/plucky.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rpotter12/whatsapp-play/HEAD/wplay/plucky.wav -------------------------------------------------------------------------------- /images/infographic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rpotter12/whatsapp-play/HEAD/images/infographic.png -------------------------------------------------------------------------------- /.gitpod/.gitpod.yml: -------------------------------------------------------------------------------- 1 | tasks: 2 | - init: pip install -r ./requirements.txt 3 | image: 4 | file: .gitpod.Dockerfile 5 | -------------------------------------------------------------------------------- /wplay/settings.cfg: -------------------------------------------------------------------------------- 1 | [auth] 2 | gmail = alias@gmail.com 3 | passw = yourpassword 4 | devid = 0000000000000000 5 | 6 | [app] 7 | pkg = com.whatsapp 8 | sig = 38a0f7d505fe18fec64fbf343ecaaaf310dbd799 9 | 10 | [client] 11 | pkg = com.google.android.gms 12 | sig = 38918a453d07199354f8b19af05ec6562ced5788 13 | ver = 9877000 -------------------------------------------------------------------------------- /wplay/utils/verify_internet.py: -------------------------------------------------------------------------------- 1 | import http.client as httplib 2 | 3 | 4 | def internet_avalaible(): 5 | """ 6 | Checks internet connection. 7 | """ 8 | conn = httplib.HTTPConnection("www.google.com", timeout=5) 9 | try: 10 | conn.request("HEAD", "/") 11 | conn.close() 12 | return True 13 | except: 14 | conn.close() 15 | return False 16 | -------------------------------------------------------------------------------- /.gitpod/.gitpod.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM custom 2 | 3 | USER gitpod 4 | 5 | # Install custom tools, runtime, etc. using apt-get 6 | # For example, the command below would install "bastet" - a command line tetris clone: 7 | # 8 | # RUN sudo apt-get -q update && # sudo apt-get install -yq bastet && # sudo rm -rf /var/lib/apt/lists/* 9 | # 10 | # More information: https://www.gitpod.io/docs/config-docker/ 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | #sudo: false 4 | 5 | python: 6 | - 3.6 7 | - 3.7 8 | - 3.8 9 | - pypy3 10 | 11 | install: 12 | - python -m pip install -U pip 13 | - pip install -r requirements.txt 14 | - pip install unittest2 15 | - python setup.py install 16 | - pip install coverage 17 | 18 | script: 19 | - python3 -m unittest discover -s tests 20 | - coverage run -m unittest discover -s tests 21 | 22 | after_success: 23 | # - codecov 24 | - bash <(curl -s https://codecov.io/bash) 25 | -------------------------------------------------------------------------------- /wplay/chat_intermediator.py: -------------------------------------------------------------------------------- 1 | # region IMPORTS 2 | from wplay import terminal_chat 3 | from wplay.utils.Logger import Logger 4 | 5 | from pathlib import Path 6 | # endregion 7 | 8 | # region LOGGER 9 | __logger = Logger(Path(__file__).name) 10 | # endregion 11 | 12 | 13 | async def intermediary(sender, receiver): 14 | """ 15 | Function to create intermediate between two person. 16 | """ 17 | __logger.info("Being and Intermediator") 18 | intermediary.rec = receiver 19 | await terminal_chat.chat(sender) 20 | -------------------------------------------------------------------------------- /wplay/utils/TODO: -------------------------------------------------------------------------------- 1 | #TODO all code: return browser.close and use it instead use exit 2 | 3 | #TODO target_search: Add a whaaaaaat menu 4 | #FIXME target_search: False positive group -> Groups name use the same div as contact status, 5 | so we need to verify if target name is in title, not in status. But, 6 | sometimes the status contain the target name and shows up as 7 | false-positive group. 8 | WHERE WE CAN FIX? __checking_group_list 9 | 10 | 11 | #TODO io: Wait for the last message to be sent before closing the browser -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | argparse>=1.4.0 2 | beautifulsoup4>=4.8.1 3 | colorama>=0.4.3 4 | DateTime>=4.3 5 | decorator>=4.4.2 6 | flake8>=3.7.9 7 | google>=2.0.3 8 | gTTS==2.1.1 9 | newsapi-python>=0.2.6 10 | phonenumbers==8.10.2 11 | playsound>=1.2.2 12 | prompt_toolkit==1.0.14 13 | psutil>=5.7.0 14 | pycodestyle>=2.6.0 15 | pycparser>=2.20 16 | pyee>=7.0.2 17 | pyfiglet>=0.8.post1 18 | pyflakes>=2.2.0 19 | Pygments>=2.6.1 20 | pyppeteer>=0.2.2 21 | python-dotenv>=0.12.0 22 | python-telegram-bot>=12.7 23 | requests>=2.22.0 24 | transitions>=0.7.2 25 | urllib3>=1.25.8 26 | websockets>=8.1 27 | whaaaaat>=0.5.2 28 | -------------------------------------------------------------------------------- /.github/IssueTemplate/FeatureRequestTemplate.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /tests/test_logger.py: -------------------------------------------------------------------------------- 1 | ''' 2 | To run tests run 3 | python3 -m unittest discover -s tests 4 | on terminal 5 | ''' 6 | #region imports 7 | import unittest 8 | from wplay.utils.Logger import Logger 9 | from pathlib import Path 10 | #endregion 11 | 12 | 13 | #region LOGGER create 14 | logger = Logger(Path(__file__).name) 15 | #endregion 16 | 17 | 18 | #region class for Logger function 19 | class CaptureLogsExample(unittest.TestCase): 20 | 21 | def test_assert_logs(self): 22 | """Verify logs using built-in self.assertLogs().""" 23 | logger.error("Testing logg class") 24 | self.assertTrue(logger, 'test_logger.py - ERROR - Testing logg class') 25 | 26 | if __name__ == '__main__': 27 | unittest.main() 28 | #endregion -------------------------------------------------------------------------------- /tests/test_kill_child_process.py: -------------------------------------------------------------------------------- 1 | ''' 2 | To run tests run 3 | python3 -m unittest discover -s tests 4 | on terminal 5 | ''' 6 | #region imports 7 | import unittest 8 | import psutil 9 | import test_func 10 | from wplay.utils.helpers import kill_child_processes 11 | #endregion 12 | 13 | 14 | #region class for test_kill_child_processes function 15 | class Testkill(unittest.TestCase): 16 | 17 | def test_kill(self): 18 | sproc = test_func.get_test_subprocess() 19 | test_pid = sproc.pid 20 | p = psutil.Process(test_pid) 21 | kill_child_processes(test_pid) 22 | p.wait() 23 | self.assertFalse(psutil.pid_exists(test_pid)) 24 | 25 | if __name__ == '__main__': 26 | unittest.main() 27 | #endregion 28 | -------------------------------------------------------------------------------- /.github/IssueTemplate/BugReportTemplate.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to 16 | 2. Click on 17 | 3. Scroll down to 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: 28 | - Browser 29 | - Version 30 | 31 | **Additional context** 32 | Add any other context about the problem here. -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | #To build docker image from this file run 2 | #docker build . 3 | #on terminal 4 | 5 | FROM python:3.6-alpine 6 | #LABEL MAINTAINER 7 | 8 | # Copying files 9 | COPY wplay/ /whatsapp-play/wplay 10 | COPY setup.py /whatsapp-play/setup.py 11 | COPY README.md /whatsapp-play/README.md 12 | COPY requirements.txt /whatsapp-play/requirements.txt 13 | 14 | # Dependencies 15 | WORKDIR /whatsapp-play 16 | RUN apk add build-base 17 | RUN apk add make 18 | RUN apk add gcc musl-dev libffi-dev openssl-dev 19 | RUN pip install cryptography==2.9.0 20 | RUN apk add --no-cache libffi-dev 21 | RUN apk add build-base 22 | RUN apk add py3-pip 23 | RUN apk add python3-dev 24 | RUN pip install cffi==1.14.0 25 | RUN pip install -r requirements.txt 26 | 27 | #ENTRYPOINT echo "Hello, welcome to whatsapp-play" 28 | ENTRYPOINT ["python3 -m wplay -h"] 29 | 30 | CMD [ "python"] -------------------------------------------------------------------------------- /wplay/utils/target_data.py: -------------------------------------------------------------------------------- 1 | # region IMPORTS 2 | from pathlib import Path 3 | 4 | from pyppeteer.page import Page 5 | 6 | from wplay.utils.helpers import whatsapp_selectors_dict 7 | from wplay.utils.Logger import Logger 8 | from wplay.utils.helpers import logs_path 9 | # endregion 10 | 11 | 12 | # region LOGGER 13 | __logger = Logger(Path(__file__).name) 14 | # endregion 15 | 16 | 17 | # region FOR SCRIPTING 18 | async def get_last_seen_from_focused_target(page: Page): 19 | __logger.info("Getting target's status information") 20 | # await page.waitForSelector(whatsapp_selectors_dict['status'], visible = True) 21 | try: 22 | status: str = await page.evaluate(f'document.querySelector("{whatsapp_selectors_dict["last_seen"]}").getAttribute("title")') 23 | return status 24 | except: 25 | return '#status not found' 26 | # endregion 27 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | # repo: rpotter12/whatsapp-play 3 | # filename: FUNDING.YML 4 | 5 | github: [rpotter12] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 6 | patreon: # Replace with a single Patreon username 7 | open_collective: # Replace with a single Open Collective username 8 | ko_fi: # Replace with a single Ko-fi username 9 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 10 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 11 | liberapay: # Replace with a single Liberapay username 12 | issuehunt: # Replace with a single IssueHunt username 13 | otechie: # Replace with a single Otechie username 14 | custom: ["https://paypal.me/rpotter12"] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /wplay/message_blast.py: -------------------------------------------------------------------------------- 1 | # region IMPORTS 2 | from pathlib import Path 3 | from typing import List 4 | 5 | from wplay.utils import browser_config 6 | from wplay.utils import target_search 7 | from wplay.utils import target_select 8 | from wplay.utils import io 9 | from wplay.utils.Logger import Logger 10 | from wplay.utils.helpers import logs_path 11 | # endregion 12 | 13 | 14 | # region LOGGER 15 | __logger = Logger(Path(__file__).name) 16 | # endregion 17 | 18 | 19 | async def message_blast(target: str): 20 | """ 21 | Sends n number of messages to the target person. 22 | """ 23 | page, _ = await browser_config.configure_browser_and_load_whatsapp() 24 | if target is not None: 25 | await target_search.search_and_select_target_all_ways(page, target) 26 | else: 27 | await target_select.manual_select_target(page) 28 | message: List[str] = io.ask_user_for_message_breakline_mode() 29 | number_of_messages: int = int(input("Enter the number of messages to blast: ")) 30 | __logger.debug("Blasting messages") 31 | for _ in range(number_of_messages): 32 | await io.send_message(page, message) 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Rohit Potter 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/Pull_Request_Template.md: -------------------------------------------------------------------------------- 1 | ## Issue that this pull request solves 2 | 3 | Closes: # (issue number) 4 | 5 | ## Proposed changes 6 | 7 | Brief description of what is fixed or changed 8 | 9 | ## Types of changes 10 | 11 | _Put an `x` in the boxes that apply_ 12 | 13 | - [ ] Bugfix (non-breaking change which fixes an issue) 14 | - [ ] New feature (non-breaking change which adds functionality) 15 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 16 | - [ ] Documentation update (Documentation content changed) 17 | - [ ] Other (please describe): 18 | 19 | ## Checklist 20 | 21 | _Put an `x` in the boxes that apply_ 22 | 23 | - [ ] My code follows the style guidelines of this project 24 | - [ ] I have performed a self-review of my own code 25 | - [ ] I have commented my code, particularly in hard-to-understand areas 26 | - [ ] I have made corresponding changes to the documentation 27 | - [ ] My changes generate no new warnings 28 | 29 | ## Screenshots 30 | 31 | Please attach the screenshots of the changes made in case of change in user interface 32 | 33 | ## Other information 34 | 35 | Any other information that is important to this pull request 36 | -------------------------------------------------------------------------------- /.github/workflows/pylint.yml: -------------------------------------------------------------------------------- 1 | name: Pylint 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: Set up Python 3.8 13 | uses: actions/setup-python@v1 14 | with: 15 | python-version: 3.8 16 | - name: Install dependencies 17 | run: | 18 | python -m pip install --upgrade pip 19 | pip install pylint 20 | - name: Analysing the code with pylint 21 | run: | 22 | # pylint `ls -R|grep .py$|xargs` 23 | pylint wplay/about_changer.py 24 | pylint wplay/broadcast_message.py 25 | pylint wplay/chat_intermediator.py 26 | pylint wplay/chatbot.py 27 | pylint wplay/download_media.py 28 | pylint wplay/get_media.py 29 | pylint wplay/get_news.py 30 | pylint wplay/message_blast.py 31 | pylint wplay/message_service.py 32 | pylint wplay/message_timer.py 33 | pylint wplay/online_tracker.py 34 | pylint wplay/profile_download.py 35 | pylint wplay/save_chat.py 36 | pylint wplay/schedule_message.py 37 | pylint wplay/target_info.py 38 | pylint wplay/telegram_bot.py 39 | pylint wplay/terminal_chat.py 40 | pylint wplay/text_to_speech.py 41 | -------------------------------------------------------------------------------- /.github/workflows/python-app.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a single version of Python 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: Python application 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Set up Python 3.9 20 | uses: actions/setup-python@v2 21 | with: 22 | python-version: 3.9 23 | - name: Install dependencies 24 | run: | 25 | python -m pip install --upgrade pip 26 | pip install flake8 pytest 27 | pip install wplay 28 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi 29 | - name: Lint with flake8 30 | run: | 31 | # stop the build if there are Python syntax errors or undefined names 32 | # flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 33 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 34 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 35 | - name: Test with pytest 36 | run: | 37 | pytest 38 | -------------------------------------------------------------------------------- /tests/test_helpers.py: -------------------------------------------------------------------------------- 1 | ''' 2 | To run tests run 3 | python3 -m unittest discover -s tests 4 | on terminal 5 | ''' 6 | #region imports 7 | import unittest 8 | from os import path 9 | from wplay.utils import helpers 10 | #endregion 11 | 12 | 13 | #region class 14 | class testPath(unittest.TestCase): 15 | 16 | def test_paths_exist(self): 17 | # self.assertTrue(path.exists(helpers.audio_file_folder_path)) 18 | # self.assertTrue(path.exists(helpers.chatbot_image_folder_path)) 19 | self.assertTrue(path.exists(helpers.data_folder_path)) 20 | self.assertTrue(path.exists(helpers.logs_path)) 21 | self.assertTrue(path.exists(helpers.log_file_path)) 22 | # self.assertTrue(path.exists(helpers.media_path)) 23 | # self.assertTrue(path.exists(helpers.messages_json_folder_path)) 24 | # self.assertTrue(path.exists(helpers.messages_json_path)) 25 | # self.assertTrue(path.exists(helpers.open_messages_json_path)) 26 | # self.assertTrue(path.exists(helpers.profile_photos_path)) 27 | # self.assertTrue(path.exists(helpers.save_chat_folder_path)) 28 | self.assertTrue(path.exists(helpers.test_log_file_path)) 29 | # self.assertTrue(path.exists(helpers.tracking_folder_path)) 30 | # self.assertTrue(path.exists(helpers.user_data_folder_path)) 31 | 32 | if __name__ == '__main__': 33 | unittest.main() 34 | #endregion 35 | -------------------------------------------------------------------------------- /wplay/profile_download.py: -------------------------------------------------------------------------------- 1 | # region IMPORTS 2 | from pathlib import Path 3 | 4 | from wplay.utils import browser_config 5 | from wplay.utils import target_search 6 | from wplay.utils import target_select 7 | from wplay.utils.helpers import profile_photos_path 8 | from wplay.utils.helpers import whatsapp_selectors_dict 9 | from wplay.utils.Logger import Logger 10 | # endregion 11 | 12 | 13 | # region LOGGER 14 | __logger = Logger(Path(__file__).name) 15 | # endregion 16 | 17 | 18 | async def get_profile_picture(target): 19 | page, _ = await browser_config.configure_browser_and_load_whatsapp() 20 | 21 | if target is not None: 22 | try: 23 | await target_search.search_and_select_target(page, target) 24 | except Exception as e: 25 | print(e) 26 | await page.reload() 27 | await target_search.search_and_select_target_without_new_chat_button(page, target) 28 | else: 29 | target = await target_select.manual_select_target(page) 30 | # Getting Profile picture url 31 | selector = '#main > header > div > div > img' 32 | await page.waitForSelector(selector, timeout=2000) 33 | image_url = await page.evaluate(f'document.querySelector("{selector}").getAttribute("src")') 34 | try: 35 | viewSource = await page.goto(image_url) 36 | f = open(profile_photos_path / f'{target}.jpg', 'wb') 37 | f.write(await viewSource.buffer()) 38 | f.close() 39 | except Exception as e: 40 | print("Error saving image") 41 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Python CircleCI 2.0 configuration file 2 | # 3 | # Check https://circleci.com/docs/2.0/language-python/ for more details 4 | # 5 | version: 2 6 | jobs: 7 | build: 8 | docker: 9 | - image: circleci/python:3.7-node-browsers-legacy 10 | 11 | steps: 12 | - checkout 13 | 14 | # Download and cache dependencies 15 | - restore_cache: 16 | keys: 17 | - v1-dependencies-{{ checksum "requirements.txt" }} 18 | # fallback to using the latest cache if no exact match is found 19 | - v1-dependencies- 20 | 21 | - run: 22 | name: install dependencies 23 | command: | 24 | python3 -m venv venv 25 | . venv/bin/activate 26 | pip install --upgrade pip 27 | pip install -r requirements.txt 28 | 29 | - save_cache: 30 | paths: 31 | - ./venv 32 | key: v1-dependencies-{{ checksum "requirements.txt" }} 33 | 34 | - run: 35 | name: run tests 36 | command: | 37 | . venv/bin/activate 38 | python3 -m unittest discover -s tests 39 | 40 | # name: run linting and metrics 41 | # command: | 42 | # . venv/bin/activate 43 | # flake8 . --count --exit-zero --max-complexity=10 --max-line-length=500 --statistics --ignore=C901,E251,E722,E231,E902 --exclude=.git,.venv,.gitignore 44 | 45 | - store_artifacts: 46 | path: test-reports 47 | destination: test-reports 48 | -------------------------------------------------------------------------------- /wplay/schedule_message.py: -------------------------------------------------------------------------------- 1 | # region IMPORTS 2 | from datetime import datetime 3 | from pathlib import Path 4 | import time 5 | import sys 6 | 7 | from wplay.utils import browser_config 8 | from wplay.utils import target_search 9 | from wplay.utils import target_select 10 | from wplay.utils import io 11 | from wplay.utils.Logger import Logger 12 | # endregion 13 | 14 | 15 | # region LOGGER 16 | __logger = Logger(Path(__file__).name) 17 | # endregion 18 | 19 | 20 | async def schedule_message(target): 21 | """ 22 | Sends message to the target person at a scheduled time. 23 | """ 24 | page, _ = await browser_config.configure_browser_and_load_whatsapp() 25 | if target is not None: 26 | await target_search.search_and_select_target(page, target) 27 | else: 28 | await target_select.manual_select_target(page) 29 | time_ = input("Enter the schedule time in HH:MM:SS format-> ") 30 | hour, minute, second = time_.split(':') 31 | current_time = datetime.now() 32 | delta_hour: int = int(hour) - current_time.hour 33 | delta_min: int = int(minute) - current_time.minute 34 | delta_second: int = int(second) - current_time.second 35 | total_seconds: int = delta_hour*3600 + delta_min*60 + delta_second 36 | if total_seconds < 0: 37 | print("Current time is ahead of the scheduled time") 38 | sys.exit() 39 | message: list[str] = io.ask_user_for_message_breakline_mode() 40 | print("Your message is scheduled at : ", time_) 41 | time.sleep(total_seconds) 42 | await io.send_message(page, message) 43 | -------------------------------------------------------------------------------- /wplay/text_to_speech.py: -------------------------------------------------------------------------------- 1 | # region IMPORTS 2 | from pathlib import Path 3 | 4 | from wplay.utils.helpers import audio_file_folder_path 5 | from wplay.utils.Logger import Logger 6 | 7 | from gtts import gTTS 8 | # endregion 9 | 10 | 11 | # region LOGGER 12 | __logger = Logger(Path(__file__).name) 13 | # endregion 14 | 15 | 16 | async def text_to_speech(target): 17 | 18 | try: 19 | __logger.info("Converting text to speech audio file") 20 | # The text that you want to convert to audio 21 | text = input("\n\nWrite the text you want to convert to audio file: ") 22 | 23 | list_laguages = ['bn: Bengali', 'de: German', 'en: English', 'es: Spanish', 'fr: French', 'gu: Gujarati', 'hi: Hindi', 24 | 'it: Italian', 'ja: Japanese', 'kn: Kannada', 'ko: Korean', 'ml: Malayalam', 'mr: Marathi', 'pt-br: Portuguese (Brazil)', 'ru: Russian', 'ta: Tamil', 'te: Telugu', 'ur: Urdu'] 25 | 26 | print('Choose a code for language of your choice from the following list\n') 27 | print(list_laguages) 28 | 29 | # Language in which you want to convert 30 | language = input("\n\nEnter the language you want to convert to audio file: ") 31 | 32 | # Passing the text and language to the engine, 33 | myobj = gTTS(text=text, lang=language, slow=False) 34 | # Saving the converted audio in a mp3 file named 35 | myobj.save(audio_file_folder_path / "{}.mp3".format(target)) 36 | 37 | except Exception as e: 38 | print(e) 39 | 40 | finally: 41 | print('\nAudio file saved in: {}/ {}.mp3'.format(audio_file_folder_path, target)) 42 | -------------------------------------------------------------------------------- /wplay/utils/Logger.py: -------------------------------------------------------------------------------- 1 | # region IMPORTS 2 | import logging 3 | from pathlib import Path 4 | from wplay.utils.helpers import log_file_path, logs_path, test_log_file_path 5 | # endregion 6 | 7 | 8 | class Logger: 9 | if not (log_file_path).exists(): 10 | logs_path.mkdir(parents=True, exist_ok=True) 11 | open(log_file_path, 'w').close() 12 | 13 | if not (test_log_file_path).exists(): 14 | logs_path.mkdir(parents=True, exist_ok=True) 15 | open(test_log_file_path, 'w').close() 16 | 17 | def __init__(self, script_name: str, level: int = logging.WARNING): 18 | self.logger = logging.getLogger(script_name) 19 | self.level = level 20 | self.logger.setLevel(self.level) 21 | 22 | if not self.logger.handlers: 23 | # Create handlers 24 | file_handler = logging.FileHandler(log_file_path) 25 | file_handler = logging.FileHandler(test_log_file_path) 26 | console = logging.StreamHandler() 27 | file_handler.setLevel(self.level) 28 | console.setLevel(self.level) 29 | 30 | # create formatter and add it to the handlers 31 | formatter = logging.Formatter('%(name)s - %(levelname)s - %(message)s') 32 | console.setFormatter(formatter) 33 | file_handler.setFormatter(formatter) 34 | 35 | # add the handlers to logger 36 | self.logger.addHandler(console) 37 | self.logger.addHandler(file_handler) 38 | 39 | def debug(self, msg: str): 40 | self.logger.debug(msg) 41 | 42 | def error(self, msg: str): 43 | self.logger.error(msg) 44 | 45 | def info(self, msg: str): 46 | self.logger.info(msg) 47 | -------------------------------------------------------------------------------- /wplay/broadcast_message.py: -------------------------------------------------------------------------------- 1 | # region IMPORTS 2 | from tkinter import Tk 3 | from tkinter.filedialog import askopenfile 4 | from pathlib import Path 5 | 6 | from wplay.utils import browser_config 7 | from wplay.utils.target_search import search_target_by_number 8 | from wplay.utils import io 9 | from typing import List 10 | from wplay.utils.helpers import data_folder_path 11 | from wplay.utils.Logger import Logger 12 | # endregion 13 | 14 | # region LOGGER 15 | __logger = Logger(Path(__file__).name) 16 | # endregion 17 | 18 | 19 | class InvalidNumber(Exception): 20 | message = "Either Number is invalid or no account exist for the number or the number was kept in wrong format :(\n" 21 | 22 | 23 | def ProcessNumbers(): 24 | __logger.info("Processing numbers.") 25 | print("Choose a text file containing full numbers with country code, one number per line.") 26 | Tk().withdraw() 27 | filename = askopenfile( 28 | initialdir=data_folder_path, 29 | title='Choose a text file with numbers.', 30 | filetypes=[("text files", "*.txt")], 31 | mode="r" 32 | ) 33 | numbers = filename.readlines() 34 | for i in range(len(numbers)): 35 | number = numbers[i].strip("\n+") 36 | numbers[i] = number 37 | return numbers 38 | 39 | 40 | async def broadcast(): 41 | __logger.info("Broadcast message.") 42 | page, _ = await browser_config.configure_browser_and_load_whatsapp() 43 | numbers = ProcessNumbers() 44 | message: List[str] = io.ask_user_for_message_breakline_mode() 45 | 46 | for number in numbers: 47 | if await search_target_by_number(page, number): 48 | await io.send_message(page, message) 49 | 50 | __logger.info("Messages broadcasted successfully!") 51 | print("Messages broadcasted successfully!") 52 | -------------------------------------------------------------------------------- /wplay/get_news.py: -------------------------------------------------------------------------------- 1 | # region IMPORTS 2 | from pathlib import Path 3 | import time 4 | 5 | from newsapi.newsapi_client import NewsApiClient 6 | 7 | from wplay.utils import browser_config 8 | from wplay.utils import target_search 9 | from wplay.utils import target_select 10 | from wplay.utils import io 11 | from wplay.utils.Logger import Logger 12 | # endregion 13 | 14 | 15 | # region LOGGER 16 | __logger = Logger(Path(__file__).name) 17 | # endregion 18 | 19 | ''' 20 | Visit https://newsapi.org/ to get your own API key. 21 | ''' 22 | newsapi = NewsApiClient(api_key="YOUR API KEY") 23 | 24 | async def get_news(target): 25 | """ 26 | Sends news as a message in every two minutes. 27 | """ 28 | def fetch_news(country_code): 29 | """ 30 | Return the title and url of the news. 31 | """ 32 | headlines = newsapi.get_top_headlines(country=country_code, language='en') 33 | url = headlines['articles'][0]['url'] 34 | title = headlines['articles'][0]['title'] 35 | return title, url 36 | 37 | page, _ = await browser_config.configure_browser_and_load_whatsapp() 38 | if target is not None: 39 | try: 40 | await target_search.search_and_select_target(page, target) 41 | except Exception as e: 42 | print(e) 43 | await target_search.search_and_select_target_without_new_chat_button(page, target) 44 | else: 45 | await target_select.manual_select_target(page) 46 | 47 | country = input("Enter your country code (ex: us or in): ") 48 | while True: 49 | try: 50 | news, source = fetch_news(country) 51 | news_ = f"*{news}* \n Full News : {source}" 52 | await io.send_message(page, news_) 53 | except Exception as e: 54 | print("Unable to get the news", e) 55 | time.sleep(120) # Sends news in every 2 min 56 | -------------------------------------------------------------------------------- /wplay/utils/io.py: -------------------------------------------------------------------------------- 1 | # region IMPORTS 2 | from pathlib import Path 3 | from typing import List, Union 4 | 5 | from pyppeteer.page import Page 6 | 7 | from wplay.utils.helpers import whatsapp_selectors_dict 8 | from wplay.utils.Logger import Logger 9 | from wplay.utils.helpers import logs_path 10 | # endregion 11 | 12 | 13 | # region LOGGER 14 | __logger = Logger(Path(__file__).name) 15 | # endregion 16 | 17 | 18 | # region FOR SCRIPTING 19 | def ask_user_for_message() -> str: 20 | return str(input("Write your message: ")) 21 | 22 | 23 | def ask_user_for_message_breakline_mode() -> List[str]: 24 | message = list() 25 | i = 0 26 | print("Write your message (Enter key to breakline)('.' alone to send):") 27 | while True: 28 | message.append(str(input())) 29 | if message[i] == '.': 30 | message.pop(i) 31 | break 32 | elif message[i] == '...' or message[i] == '#_FILE' or message[i] == '#_TTS' or message[i] == '#_FWD': 33 | break 34 | i += 1 35 | return message 36 | 37 | 38 | async def send_message(page: Page, message: Union[List[str], str]): 39 | __logger.debug("Sending message") 40 | for i in range(len(message)): 41 | await page.type(whatsapp_selectors_dict['message_area'], message[i]) 42 | if isinstance(message, list): 43 | await page.keyboard.down('Shift') 44 | await page.keyboard.press('Enter') 45 | await page.keyboard.up('Shift') 46 | await page.keyboard.press('Enter') 47 | 48 | 49 | async def send_file(page): 50 | __logger.info("Sending File") 51 | await page.click(whatsapp_selectors_dict['attach_file']) 52 | await page.click(whatsapp_selectors_dict['choose_file']) 53 | await page.waitForSelector(whatsapp_selectors_dict['send_file'], timeout=30000) 54 | await page.click(whatsapp_selectors_dict['send_file']) 55 | # endregion 56 | -------------------------------------------------------------------------------- /wplay/get_media.py: -------------------------------------------------------------------------------- 1 | # region Imports 2 | import time 3 | from pathlib import Path 4 | 5 | from wplay.utils import browser_config 6 | from wplay.utils.Logger import Logger 7 | from wplay.utils.helpers import profile_photos_path 8 | # endregion 9 | 10 | 11 | # region LOGGER 12 | __logger = Logger(Path(__file__).name) 13 | # endregion 14 | 15 | 16 | async def get_profile_photos(): 17 | """ 18 | Download the profile picture of all the contacts. 19 | """ 20 | page, _ = await browser_config.configure_browser_and_load_whatsapp() 21 | total_contacts = int(input("Please provide total whatsapp contacts: ")) 22 | loop = round(total_contacts/7) 23 | images_list = [] 24 | 25 | await page.waitForSelector('#pane-side > div:nth-child(1) > div > div > div:nth-child(1) > div > div > div > div > img') 26 | 27 | for c in range(loop): 28 | for i in range(1, 18): 29 | selector = f"#pane-side > div:nth-child(1) > div > div > div:nth-child({i}) > div > div > div > div > img" 30 | try: 31 | await page.waitForSelector(selector, timeout=2000) 32 | image_url = await page.evaluate(f'document.querySelector("{selector}").getAttribute("src")') 33 | print(f"{c}:{i}-{image_url}") 34 | if image_url not in images_list: 35 | images_list.append(image_url) 36 | except Exception as e: 37 | print(e) 38 | print("No profile image found") 39 | await page.evaluate("document.querySelector('#pane-side').scrollBy(0, 500)") 40 | 41 | for count in range(len(images_list)): 42 | try: 43 | viewSource = await page.goto(images_list[count]) 44 | f = open(profile_photos_path / f'{count}.jpg', 'wb') 45 | f.write(await viewSource.buffer()) 46 | f.close() 47 | except Exception as e: 48 | print("Error saving image") 49 | 50 | print("Saved all the images to media_folder.") 51 | time.sleep(5) 52 | -------------------------------------------------------------------------------- /wplay/message_timer.py: -------------------------------------------------------------------------------- 1 | # region IMPORTS 2 | import time 3 | import random 4 | from pathlib import Path 5 | 6 | from wplay.utils import browser_config 7 | from wplay.utils import target_search 8 | from wplay.utils import target_select 9 | from wplay.utils import io 10 | from wplay.utils.Logger import Logger 11 | from wplay.utils.helpers import logs_path 12 | # endregion 13 | 14 | 15 | # region LOGGER 16 | __logger = Logger(Path(__file__).name) 17 | # endregion 18 | 19 | 20 | async def message_timer(target): 21 | """ 22 | Sends message in a particular time interval. 23 | """ 24 | page, _ = await browser_config.configure_browser_and_load_whatsapp() 25 | if target is not None: 26 | try: 27 | await target_search.search_and_select_target(page, target) 28 | except Exception as e: 29 | print(e) 30 | await page.reload() 31 | await target_search.search_and_select_target_without_new_chat_button(page, target) 32 | else: 33 | await target_select.manual_select_target(page) 34 | # Region inputs 35 | __logger.info("Input message information for message timer") 36 | message_type_numbers: int = int(input("How many types of messages will you send? ")) 37 | messages: list[str] = list() 38 | for _ in range(message_type_numbers): 39 | messages.append(io.ask_user_for_message_breakline_mode()) 40 | number_of_messages: int = int(input("Enter the number of messages to send: ")) 41 | minimumTimeInterval: int = int(input("Enter minimum interval number in seconds: ")) 42 | maximumTimeInterval: int = int(input("Enter maximum interval number in seconds: ")) 43 | # Endregion 44 | 45 | random.seed() 46 | for _ in range(number_of_messages): 47 | if not messages: 48 | break 49 | await io.send_message(page, messages[random.randrange(0, message_type_numbers)]) 50 | if minimumTimeInterval != maximumTimeInterval: 51 | time.sleep(random.randrange(minimumTimeInterval, maximumTimeInterval)) 52 | else: 53 | time.sleep(minimumTimeInterval) 54 | -------------------------------------------------------------------------------- /wplay/save_chat.py: -------------------------------------------------------------------------------- 1 | # region IMPORTS 2 | from pathlib import Path 3 | 4 | from wplay.utils import browser_config 5 | from wplay.utils import target_search 6 | from wplay.utils import target_select 7 | from wplay.utils.helpers import save_chat_folder_path 8 | from wplay.utils.Logger import Logger 9 | # endregion 10 | 11 | 12 | # region LOGGER 13 | __logger = Logger(Path(__file__).name) 14 | # endregion 15 | 16 | 17 | async def save_chat(target): 18 | """ 19 | Save the whole chat of the target person in .txt file. 20 | """ 21 | page, _ = await browser_config.configure_browser_and_load_whatsapp() 22 | 23 | if target is not None: 24 | try: 25 | await target_search.search_and_select_target(page, target) 26 | except Exception as e: 27 | print(e) 28 | await page.reload() 29 | await target_search.search_and_select_target_without_new_chat_button(page, target) 30 | else: 31 | target = await target_select.manual_select_target(page) 32 | 33 | # selectors 34 | selector_values = "#main > div > div > div > div > div > div > div > div" 35 | selector_sender = "#main > div > div > div > div > div > div > div > div > div.copyable-text" 36 | 37 | # Getting all the messages of the chat 38 | try: 39 | __logger.info("Saving chats with target") 40 | await page.waitForSelector(selector_values) 41 | values = await page.evaluate(f'''() => [...document.querySelectorAll('{selector_values}')] 42 | .map(element => element.textContent)''') 43 | sender = await page.evaluate(f'''() => [...document.querySelectorAll('{selector_sender}')] 44 | .map(element => element.getAttribute("data-pre-plain-text"))''') 45 | 46 | final_values = [x[:-8] for x in values] 47 | new_list = [a + b for a, b in zip(sender, final_values)] 48 | 49 | # opens chat file of the target person 50 | with open(save_chat_folder_path / f'chat_{target}.txt', 'w') as output: 51 | for s in new_list: 52 | output.write("%s\n" % s) 53 | 54 | except Exception as e: 55 | print(e) 56 | 57 | finally: 58 | # save the chat and close the file 59 | output.close() 60 | print(f'\nChat file saved in: {str(save_chat_folder_path/"chat_")}{target}.txt') 61 | -------------------------------------------------------------------------------- /wplay/utils/target_select.py: -------------------------------------------------------------------------------- 1 | # region IMPORTS 2 | from pathlib import Path 3 | 4 | from pyppeteer.page import Page 5 | 6 | from wplay.utils import target_search 7 | from wplay.utils.Logger import Logger 8 | from wplay.utils.helpers import whatsapp_selectors_dict 9 | # endregion 10 | 11 | 12 | # region FOR SCRIPTING 13 | async def manual_select_target(page: Page, hide_groups: bool = False): 14 | __print_manual_selection_info() 15 | await __open_new_chat(page) 16 | target_focused_title = await __get_focused_target_title(page) 17 | await __wait_for_message_area(page) 18 | __print_selected_target_title(target_focused_title) 19 | complete_target_info = await target_search.__get_complete_info_on_target(page) 20 | target_search.__print_complete_target_info(complete_target_info) 21 | await __close_contact_info_page(page) 22 | return target_focused_title 23 | # endregion 24 | 25 | 26 | # region SELECT TARGET 27 | def __print_manual_selection_info(): 28 | print(f"You've to go to whatsapp web and select target manually") 29 | 30 | 31 | def __print_selected_target_title(target_focused_title: str): 32 | print(f"You've selected the target named by: {target_focused_title}") 33 | 34 | 35 | async def __close_contact_info_page(page: Page): 36 | try: 37 | await page.waitForSelector( 38 | whatsapp_selectors_dict['contact_info_page_close_button'], 39 | visible=True, 40 | timeout=5000 41 | ) 42 | await page.click(whatsapp_selectors_dict['contact_info_page_close_button']) 43 | except Exception as e: 44 | print(e) 45 | 46 | 47 | async def __open_new_chat(page: Page): 48 | await page.waitForSelector( 49 | whatsapp_selectors_dict['new_chat_button'], 50 | visible=True, 51 | timeout=0 52 | ) 53 | 54 | 55 | async def __get_focused_target_title(page: Page): 56 | try: 57 | await page.waitForSelector(whatsapp_selectors_dict['target_focused_title'], visible=True, timeout=0) 58 | target_focused_title = await page.evaluate(f'document.querySelector("{whatsapp_selectors_dict["target_focused_title"]}").getAttribute("title")') 59 | except Exception as e: 60 | print(f'No target selected! Error: {str(e)}') 61 | exit() 62 | return target_focused_title 63 | 64 | 65 | async def __wait_for_message_area(page: Page): 66 | try: 67 | await page.waitForSelector(whatsapp_selectors_dict['message_area'], timeout=0) 68 | except Exception as e: 69 | print(f"You don't belong this group anymore! Error: {str(e)}") 70 | # endregion 71 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | with open("README.md", "r") as f: 4 | long_description = f.read() 5 | 6 | setup( 7 | name="wplay", 8 | version="8.0.7", 9 | install_requires=["argparse >= 1.4.0", 10 | "beautifulsoup4>=4.8.1", 11 | "colorama>=0.4.3", 12 | "dateTime>=4.3", 13 | "decorator>=4.4.2", 14 | "flake8>=3.7.9", 15 | "google>=2.0.3", 16 | "gTTS==2.1.1", 17 | "newsapi-python>=0.2.6", 18 | "phonenumbers==8.10.2", 19 | "playsound>=1.2.2", 20 | "prompt_toolkit==1.0.14", 21 | "psutil>=5.7.0", 22 | "pyfiglet>=0.8.post1", 23 | "pyflakes>=2.2.0", 24 | "Pygments>=2.6.1", 25 | "pyppeteer>=0.0.25", 26 | "python-dotenv==0.12.0", 27 | "python-telegram-bot>=11.1.0", 28 | "requests>=2.22.0", 29 | "transitions>=0.7.2", 30 | "urllib3>=1.25.8", 31 | "websockets>=8.1", 32 | "whaaaaat>=0.5.2", 33 | ], 34 | packages=find_packages(), 35 | description="command line software to play with your WhatsApp", 36 | long_description=long_description, 37 | long_description_content_type="text/markdown", 38 | author="Rohit Potter, Alexandre Calil", 39 | author_email="rohitpotter12@gmail.com, alexandrecalilmf@gmail.com", 40 | license="MIT", 41 | python_requires=">=3.6", 42 | url="https://github.com/rpotter12/whatsapp-play/", 43 | download_url="https://pypi.org/project/wplay/", 44 | keywords=[ 45 | "whatsapp", 46 | "whatsapp cli", 47 | "whatsapp api", 48 | "whatsapp service" 49 | "whatsapp chat", 50 | "message blast", 51 | "message timer", 52 | "whatsapp terminal", 53 | "whatsapp news", 54 | "whatsapp schedule", 55 | "tracker", 56 | "online tracking", 57 | "save-chat" 58 | ], 59 | classifiers=[ 60 | "Programming Language :: Python", 61 | "Programming Language :: Python :: 3", 62 | "Programming Language :: Python :: 3.6", 63 | "Programming Language :: Python :: 3.7", 64 | "Programming Language :: Python :: 3.8", 65 | "Programming Language :: Python :: 3 :: Only", 66 | "License :: OSI Approved :: MIT License", 67 | "Operating System :: OS Independent", 68 | ], 69 | entry_points={"console_scripts": ["wplay = wplay.__main__:main"]}, 70 | ) 71 | -------------------------------------------------------------------------------- /wplay/telegram_bot.py: -------------------------------------------------------------------------------- 1 | # region IMPORTS 2 | import tkinter 3 | from tkinter import filedialog 4 | from pathlib import Path 5 | import pickle 6 | 7 | from telegram.ext import CommandHandler, Updater 8 | from wplay.utils.helpers import data_folder_path 9 | from wplay.utils.Logger import Logger 10 | # endregion 11 | 12 | 13 | # region LOGGER 14 | __logger = Logger(Path(__file__).name) 15 | # endregion 16 | 17 | 18 | status_file_path = None 19 | 20 | 21 | def start_tkinter(): 22 | root_window = tkinter.Tk() 23 | root_window.withdraw() 24 | 25 | 26 | def ask_where_are_the_status_file(): 27 | print('Choose a status text file.') 28 | status_file_path = filedialog.askopenfile( 29 | initialdir=data_folder_path / 'tracking_data', 30 | title='Choose a status text file.', 31 | filetypes=(("text files", "*.txt"), ("all files", "*.*")) 32 | ) 33 | if status_file_path == (): 34 | print("Error! Choose a status.") 35 | exit() 36 | return status_file_path 37 | 38 | 39 | def startmessage(bot, update): 40 | chat_id: int = update.message.chat_id 41 | text: str = ''' 42 | Hi, I am here to send all tracked online status in whatsapp :) 43 | ''' 44 | bot.send_message(chat_id=chat_id, text=text) 45 | 46 | 47 | def send_status(bot, update): 48 | # Display last updated online status message 49 | chat_id = update.message.chat_id 50 | try: 51 | f = open(status_file_path, 'r') 52 | file_data = f.readlines() 53 | text: Union[str, bytes] = file_data[len(file_data) - 1] 54 | bot.send_message(chat_id=chat_id, text=text) 55 | except Exception as e: 56 | print(e) 57 | bot.send_message(chat_id=chat_id, text='oops! An error occurred') 58 | 59 | 60 | def telegram_status(name): 61 | print(name) 62 | start_tkinter() 63 | global status_file_path 64 | status_file_path = ask_where_are_the_status_file() 65 | # Add bot token 66 | global TOKEN 67 | new_token = False 68 | token_file_path = "wplay/telegram_token.pkl" 69 | if Path(token_file_path).exists(): 70 | user_choice = input("Do you want to use last saved token (Y) or enter new token (N): ") 71 | if user_choice in "Yy": 72 | with open(token_file_path, "rb") as token_file: 73 | TOKEN = pickle.load(token_file) 74 | else: 75 | new_token = True 76 | else: 77 | new_token = True 78 | if new_token: 79 | TOKEN = input("Enter token: ") 80 | with open(token_file_path, "wb") as token_file: 81 | pickle.dump(TOKEN, token_file) 82 | 83 | # Added all the essential command handlers 84 | updater = Updater(TOKEN, use_context=True) 85 | dp = updater.dispatcher 86 | dp.add_handler(CommandHandler('start', startmessage)) 87 | dp.add_handler(CommandHandler('status', send_status)) 88 | updater.start_polling() 89 | updater.idle() 90 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Personal Data 2 | .userData 3 | tracking_data 4 | data 5 | telegram_token.pkl 6 | 7 | # Byte-compiled / optimized / DLL files 8 | wplay.egg-info/ 9 | __pycache__/ 10 | *.pyc 11 | __pycache__/ 12 | src/\.DS_Store 13 | src/__pycache__/ 14 | src/logs/ 15 | src/test/__pycache__/ 16 | \.DS_Store 17 | \.idea/ 18 | src/XDG_CACHE_HOME/ 19 | *.py[cod] 20 | *$py.class 21 | 22 | # C extensions 23 | *.so 24 | 25 | # Distribution / packaging 26 | .Python 27 | ImplementationTests 28 | build/ 29 | develop-eggs/ 30 | dist/ 31 | downloads/ 32 | eggs/ 33 | .eggs/ 34 | lib/ 35 | lib64/ 36 | parts/ 37 | sdist/ 38 | var/ 39 | wheels/ 40 | pip-wheel-metadata/ 41 | share/python-wheels/ 42 | *.egg-info/ 43 | .installed.cfg 44 | *.egg 45 | MANIFEST 46 | 47 | # PyInstaller 48 | # Usually these files are written by a python script from a template 49 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 50 | *.manifest 51 | *.spec 52 | 53 | # Installer logs 54 | pip-log.txt 55 | pip-delete-this-directory.txt 56 | 57 | # Unit test / coverage reports 58 | htmlcov/ 59 | .tox/ 60 | .nox/ 61 | .coverage 62 | .coverage.* 63 | .cache 64 | nosetests.xml 65 | coverage.xml 66 | *.cover 67 | .hypothesis/ 68 | .pytest_cache/ 69 | 70 | # Translations 71 | *.mo 72 | *.pot 73 | 74 | # Django stuff: 75 | *.log 76 | local_settings.py 77 | db.sqlite3 78 | db.sqlite3-journal 79 | 80 | # Flask stuff: 81 | instance/ 82 | .webassets-cache 83 | 84 | # Scrapy stuff: 85 | .scrapy 86 | 87 | # Sphinx documentation 88 | docs/_build/ 89 | 90 | # PyBuilder 91 | target/ 92 | 93 | # Jupyter Notebook 94 | .ipynb_checkpoints 95 | 96 | # IPython 97 | profile_default/ 98 | ipython_config.py 99 | 100 | # pyenv 101 | .python-version 102 | 103 | # pipenv 104 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 105 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 106 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 107 | # install all needed dependencies. 108 | #Pipfile.lock 109 | 110 | # celery beat schedule file 111 | celerybeat-schedule 112 | 113 | # SageMath parsed files 114 | *.sage.py 115 | 116 | # Environments 117 | .env 118 | .venv 119 | env/ 120 | venv/ 121 | ENV/ 122 | env.bak/ 123 | venv.bak/ 124 | 125 | # Spyder project settings 126 | .spyderproject 127 | .spyproject 128 | 129 | # Rope project settings 130 | .ropeproject 131 | 132 | # mkdocs documentation 133 | /site 134 | 135 | # mypy 136 | .mypy_cache/ 137 | .dmypy.json 138 | dmypy.json 139 | 140 | # Pyre type checker 141 | .pyre/ 142 | 143 | # Visual Studio 144 | .vs 145 | 146 | # Visual Studio Code 147 | .vscode 148 | 149 | 150 | # React 151 | # dependencies 152 | /node_modules 153 | /.pnp 154 | .pnp.js 155 | 156 | # testing 157 | /coverage 158 | 159 | # production 160 | /build 161 | 162 | # misc 163 | .DS_Store 164 | .env.local 165 | .env.development.local 166 | .env.test.local 167 | .env.production.local 168 | 169 | npm-debug.log* 170 | yarn-debug.log* 171 | yarn-error.log* 172 | -------------------------------------------------------------------------------- /wplay/about_changer.py: -------------------------------------------------------------------------------- 1 | # region IMPORTS 2 | import time 3 | from pathlib import Path 4 | from wplay.utils.helpers import whatsapp_selectors_dict 5 | from wplay.utils import browser_config 6 | from wplay.utils.Logger import Logger 7 | from newsapi.newsapi_client import NewsApiClient 8 | # endregion 9 | 10 | 11 | # region LOGGER 12 | __logger = Logger(Path(__file__).name) 13 | # endregion 14 | 15 | 16 | # Asking user 17 | async def about_changer(): 18 | option = input("Choose(1/2) \n1.Write new about \n2.Change about with latest headline\n") 19 | if option == '1': 20 | await change_about() 21 | else: 22 | await about_changer_news() 23 | 24 | 25 | # Custom About 26 | async def change_about(): 27 | page, _ = await browser_config.configure_browser_and_load_whatsapp() 28 | # opens photo element 29 | await page.waitForSelector(whatsapp_selectors_dict['profile_photo_element'], visible=True) 30 | await page.click(whatsapp_selectors_dict['profile_photo_element']) 31 | await page.waitForSelector(whatsapp_selectors_dict['about_edit_button_element']) 32 | await page.click(whatsapp_selectors_dict['about_edit_button_element']) 33 | status = input("Enter your new about: ") 34 | __logger.info("Writing About") 35 | # Write about 36 | await page.type(whatsapp_selectors_dict['about_text_area'], status) 37 | await page.keyboard.press('Enter') 38 | print("About changed to {}".format(status)) 39 | 40 | 41 | async def get_api_key(): 42 | __logger.info("Getting key") 43 | print("Visit https://newsapi.org/ to get your own API key") 44 | key = input("Enter you API KEY : ") 45 | get_api_key.newsapi = NewsApiClient(api_key='{}'.format(key)) 46 | 47 | 48 | # News in About 49 | async def about_changer_news(): 50 | page, _ = await browser_config.configure_browser_and_load_whatsapp() 51 | await get_api_key() 52 | query: str = str(input("What's the news theme? : ")) 53 | # opens photo element 54 | await page.waitForSelector(whatsapp_selectors_dict['profile_photo_element'], visible=True) 55 | await page.click(whatsapp_selectors_dict['profile_photo_element']) 56 | news = '' 57 | while True: 58 | current_news = str(fetch_news(query)) 59 | print(current_news) 60 | if news != current_news: 61 | # Click on edit about button 62 | await page.waitForSelector(whatsapp_selectors_dict['about_edit_button_element']) 63 | await page.click(whatsapp_selectors_dict['about_edit_button_element']) 64 | for _ in range(140): 65 | await page.keyboard.press('Backspace') 66 | news = current_news 67 | __logger.info("Updating About latest news") 68 | # Write about 69 | await page.type(whatsapp_selectors_dict['about_text_area'], news) 70 | await page.keyboard.press('Enter') 71 | # News get updated every 15 minutes by newsapi.org 72 | # Added extra minute for buffer period 73 | # For free account : max limit is 500 request/day 74 | time.sleep(905) 75 | 76 | 77 | def fetch_news(query): 78 | __logger.info("Fetching news") 79 | top_headlines = get_api_key.newsapi.get_top_headlines(q=query, language='en') 80 | return top_headlines['articles'][0]['title'] 81 | -------------------------------------------------------------------------------- /wplay/online_tracker.py: -------------------------------------------------------------------------------- 1 | import time 2 | from pathlib import Path 3 | from datetime import datetime 4 | from playsound import playsound 5 | from wplay.utils import browser_config 6 | from wplay.utils import target_search 7 | from wplay.utils import target_select 8 | from wplay.utils import target_data 9 | from wplay.utils.helpers import tracking_folder_path 10 | from wplay.utils.Logger import Logger 11 | from wplay.utils.helpers import logs_path 12 | 13 | 14 | # region LOGGER 15 | __logger = Logger(Path(__file__).name) 16 | # endregion 17 | 18 | 19 | async def tracker(target): 20 | """ 21 | This function checks the online and offline status of the target person. 22 | """ 23 | page, _ = await browser_config.configure_browser_and_load_whatsapp() # open bot browser and load whatsapp web website 24 | if target is not None: # checks if target is not none then it search for the target and select it 25 | try: 26 | target_name = await target_search.search_and_select_target(page, target, hide_groups=True) 27 | except Exception as e: 28 | print(e) 29 | await page.reload() 30 | target_name = await target_search.search_and_select_target_without_new_chat_button(page, target, hide_groups=True) 31 | else: # if target is none then it allow user to select target manually from browser 32 | target_name = await target_select.manual_select_target(page, hide_groups=True) 33 | 34 | # opens status file of the target person 35 | status_file: str = open(tracking_folder_path / f'status_{target_name}.txt', 'w').close() 36 | status_file: str = open(tracking_folder_path / f'status_{target_name}.txt', 'a') 37 | 38 | # default assignes 39 | is_sound_enabled: bool = True 40 | last_status: str = 'offline' 41 | try: 42 | print(f'Tracking: {target_name}') 43 | __logger.info("Tracking target") 44 | status_file.write(f'Tracking: {target_name}\n') 45 | while True: 46 | status: str = await target_data.get_last_seen_from_focused_target(page) # checks last seen 47 | if status == 'online': # if last seen is online then shows online 48 | is_online: bool = True 49 | else: # if nothing is there so shows offline 50 | is_online: bool = False 51 | status: str = 'offline' 52 | # play a notification sound on online 53 | if last_status != is_online: 54 | if is_online: 55 | try: 56 | if is_sound_enabled: 57 | playsound('plucky.wav') 58 | except Exception as e: 59 | print("Error: Couldn't play the sound.") 60 | is_sound_enabled: bool = False 61 | print( 62 | f'{datetime.now().strftime("%d/%m/%Y, %H:%M:%S")}' + f' - Status: {status}' 63 | ) 64 | status_file.write( 65 | f'{datetime.now().strftime("%d/%m/%Y, %H:%M:%S")}' + f' - Status: {status}\n') 66 | last_status: str = is_online 67 | time.sleep(0.5) 68 | except KeyboardInterrupt: 69 | __logger.error("User Pressed Ctrl+C") 70 | finally: 71 | # save the status and close the file 72 | status_file.close() 73 | print(f'\nStatus file saved in: {str(tracking_folder_path/"status_")}{target_name}.txt') 74 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at rohitpotter12@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /wplay/chatbot.py: -------------------------------------------------------------------------------- 1 | from googlesearch import search 2 | from pyppeteer import launch 3 | from wplay.utils.helpers import chatbot_image_folder_path 4 | 5 | 6 | async def Bot(last_Message): 7 | """ 8 | Function to perform instruction as instructed to bot. 9 | """ 10 | print('\n Bot activated') 11 | first_last_Message = "".join(last_Message.split()) 12 | simple_menu = { 13 | "hi": say_hi, 14 | "help": _help_commands, 15 | "goodmorning": say_goodmorning, 16 | "goodnight": say_goodnight, 17 | "howareyou?": say_fine, 18 | } 19 | simple_menu_keys = simple_menu.keys() 20 | result = [] 21 | 22 | try: 23 | command_args = first_last_Message[1:].split(" ", 1) 24 | command_arg = last_Message[1:].split(" ", 1) 25 | 26 | if len(command_args) == 1 and command_args[0] in simple_menu_keys: 27 | return simple_menu[command_args[0]]() 28 | 29 | elif command_arg[0] == 'google': 30 | query = "".join(command_arg[1]) 31 | for j in search(query, tld="co.in", num=10, stop=10, pause=2): 32 | result.append(j) 33 | print("Sending links for query") 34 | return result 35 | 36 | elif command_arg[0] == "image": 37 | query = "".join(command_arg[1]) 38 | await takeScreenshot(query) 39 | print("Taking screenshot of google image for query") 40 | return "Sending you screenshot" 41 | 42 | elif command_arg[0] == "maps": 43 | query = "".join(command_arg[1]) 44 | map_parameters_list = query.replace(" ", "") 45 | map_parameters = map_parameters_list.split(',') 46 | base_url = "https://www.google.com/maps/dir/?api=1&" 47 | 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]) 48 | print("Sending link for google maps") 49 | return custom_url 50 | 51 | else: 52 | return "Wrong command. Send me /help to see a list of valid commands" 53 | 54 | except KeyError as e: 55 | print("Key Error Exception: {err}".format(err=str(e))) 56 | 57 | 58 | def say_hi(): 59 | print("Saying hi") 60 | return "Wplay chatbot says hi! Hope you are having a nice day..." 61 | 62 | 63 | def say_goodmorning(): 64 | print("Saying good morning") 65 | return "Bot says Good Morning! Have a Good Day..." 66 | 67 | 68 | def say_goodnight(): 69 | print("Saying good night") 70 | return "Bot says Good Night! Sweet Dreams..." 71 | 72 | 73 | def say_fine(): 74 | print("Saying I am Fine!") 75 | return "Bot says I am Fine Thank You! How are you?" 76 | 77 | 78 | def _help_commands(): 79 | print("Asking for help") 80 | return "How may I assist you with help\n"\ 81 | "List of commands:\n" \ 82 | "/hi (bot says hi), " \ 83 | "/all_commands (ist of all commands), " \ 84 | "/good morning, " \ 85 | "/good night, " \ 86 | "/how are you? " \ 87 | "/google {query} " \ 88 | "/image {query} " \ 89 | "/maps {origin}, {destination}, {mode:driving/bicycling/transit/two-wheeler/walking}" 90 | 91 | 92 | async def takeScreenshot(qry): 93 | browser = await launch() 94 | page = await browser.newPage() 95 | await page.goto('https://www.google.com/search?q={}&source=lnms&tbm=isch'.format(qry)) 96 | image_path = str(chatbot_image_folder_path / '{}.png'.format(qry)) 97 | await page.screenshot({'path': image_path}) 98 | await browser.close() 99 | -------------------------------------------------------------------------------- /wplay/message_service.py: -------------------------------------------------------------------------------- 1 | # region IMPORTS 2 | from pathlib import Path 3 | import threading 4 | import time 5 | import json 6 | 7 | from wplay.utils import browser_config 8 | from wplay.utils.target_search import search_target_by_number 9 | from wplay.utils import target_select 10 | from wplay.utils import io 11 | from wplay.utils import helpers 12 | from wplay.utils import verify_internet 13 | from wplay.utils.Logger import Logger 14 | from wplay.utils.MessageStack import MessageStack 15 | # endregion 16 | 17 | 18 | # region LOGGER 19 | import logging 20 | __logger = Logger(Path(__file__).name, logging.DEBUG) 21 | # endregion 22 | 23 | 24 | """ 25 | Messages file structure, your program should add messages this way 26 | inside the file "messages.json" located at user/wplay/messagesJSON folder. 27 | 28 | { 29 | "messages": [ 30 | { 31 | "uuid": "33bf7c667f8011ea96971c3947562893", 32 | "number": "5562999999999", 33 | "message": "*Bold Hello World*" 34 | }, 35 | { 36 | "uuid": "46ca6d284f8058ee89354e2987862869", 37 | "number": "5562888888888", 38 | "message": ["Hello!!!","Multi-line"] 39 | } 40 | ] 41 | } 42 | """ 43 | 44 | 45 | async def message_service(): 46 | page, _ = await browser_config.configure_browser_and_load_whatsapp() 47 | __logger.info("Message Service On.") 48 | print("Message Service is ON, press CTRL+C to stop.") 49 | print("Listening for messages in file 'messages.json' inside user/wplay/messagesJSON folder.") 50 | # Initialize a instance of MessageStack 51 | message_stack = MessageStack() 52 | # Move all messages from open_messages.json to messages.json when the program starts 53 | message_stack.move_all_messages(helpers.open_messages_json_path, helpers.messages_json_path) 54 | 55 | while True: 56 | if verify_internet.internet_avalaible(): 57 | try: 58 | # Try to get the message 59 | current_msg = next(message_stack.get_message()) 60 | 61 | # Move message from messages.json to open_messages.json 62 | message_stack.move_message(helpers.messages_json_path, helpers.open_messages_json_path, current_msg['uuid']) 63 | 64 | try: 65 | if await search_target_by_number(page, current_msg['number']): 66 | await io.send_message(page, current_msg['message']) 67 | message_stack.remove_message(current_msg['uuid'], helpers.open_messages_json_path) 68 | except ValueError: 69 | __logger.debug("Wrong JSON Formatting. Message Deleted.") 70 | message_stack.remove_message(current_msg['uuid'], helpers.open_messages_json_path) 71 | except Exception as e: 72 | # If any error occurs that is not because of wrong data, 73 | # the message will be moved back to messages.json 74 | __logger.error(f'Error handling and sending the message: {str(e)}') 75 | MessageStack().move_message(helpers.open_messages_json_path, helpers.messages_json_path, current_msg['uuid']) 76 | except (StopIteration, json.JSONDecodeError): 77 | # if there are no messages to catch we will have this 'Warning', will try again after a time 78 | time.sleep(1) 79 | else: 80 | __logger.debug('Internet is not available, trying again after 15 seconds.') 81 | time.sleep(15) 82 | 83 | # Move messages from open_messages.json to messages.json that wasn't sended. 84 | message_stack.move_all_messages(helpers.open_messages_json_path, helpers.messages_json_path) 85 | -------------------------------------------------------------------------------- /wplay/target_info.py: -------------------------------------------------------------------------------- 1 | # region IMPORTS 2 | from wplay.utils import browser_config 3 | from wplay.utils import target_search 4 | from wplay.utils import target_select 5 | import phonenumbers 6 | from phonenumbers import carrier 7 | from phonenumbers import geocoder 8 | from phonenumbers import timezone 9 | import re 10 | import sys 11 | from pathlib import Path 12 | from wplay.utils.Logger import Logger 13 | # end IMPORTS 14 | 15 | 16 | # region LOGGER 17 | __logger = Logger(Path(__file__).name) 18 | # endregion 19 | 20 | 21 | def formatNumber(InputNumber): 22 | return re.sub(r"(?:\+)?(?:[^[0-9]*)", "", InputNumber) 23 | 24 | 25 | def localScan(InputNumber, print_results=True): 26 | print("Running local scan...") 27 | 28 | FormattedPhoneNumber = "+" + formatNumber(InputNumber) 29 | 30 | try: 31 | PhoneNumberObject = phonenumbers.parse(FormattedPhoneNumber, None) 32 | except Exception as e: 33 | print(e) 34 | else: 35 | if not phonenumbers.is_valid_number(PhoneNumberObject): 36 | return False 37 | 38 | number = phonenumbers.format_number(PhoneNumberObject, phonenumbers.PhoneNumberFormat.E164).replace("+", "") 39 | numberCountryCode = phonenumbers.format_number(PhoneNumberObject, phonenumbers.PhoneNumberFormat.INTERNATIONAL).split(" ")[0] 40 | numberCountry = phonenumbers.region_code_for_country_code(int(numberCountryCode)) 41 | 42 | localNumber = phonenumbers.format_number(PhoneNumberObject, phonenumbers.PhoneNumberFormat.E164).replace(numberCountryCode, "") 43 | internationalNumber = phonenumbers.format_number(PhoneNumberObject, phonenumbers.PhoneNumberFormat.INTERNATIONAL) 44 | 45 | country = geocoder.country_name_for_number(PhoneNumberObject, "en") 46 | location = geocoder.description_for_number(PhoneNumberObject, "en") 47 | carrierName = carrier.name_for_number(PhoneNumberObject, "en") 48 | 49 | if print_results: 50 | print("International format: {}".format(internationalNumber)) 51 | print("Local format: {}".format(localNumber)) 52 | print("Country found: {} ({})".format(country, numberCountryCode)) 53 | print("City/Area: {}".format(location)) 54 | print("Carrier: {}".format(carrierName)) 55 | for timezoneResult in timezone.time_zones_for_number(PhoneNumberObject): 56 | print("Timezone: {}".format(timezoneResult)) 57 | 58 | if phonenumbers.is_possible_number(PhoneNumberObject): 59 | print("The number is valid and possible.") 60 | else: 61 | print("The number is valid but might not be possible.") 62 | 63 | numberObj = {} 64 | numberObj["input"] = InputNumber 65 | numberObj["default"] = number 66 | numberObj["local"] = localNumber 67 | numberObj["international"] = internationalNumber 68 | numberObj["country"] = country 69 | numberObj["countryCode"] = numberCountryCode 70 | numberObj["countryIsoCode"] = numberCountry 71 | numberObj["location"] = location 72 | numberObj["carrier"] = carrierName 73 | 74 | return numberObj 75 | 76 | 77 | def scanNumber(InputNumber): 78 | print("[!] ---- Fetching informations for {} ---- [!]".format(formatNumber(InputNumber))) 79 | number = localScan(InputNumber) 80 | 81 | if not number: 82 | print(("Error: number {} is not valid. Skipping.".format(formatNumber(InputNumber)))) 83 | sys.exit() 84 | 85 | print("Scan finished.") 86 | 87 | 88 | def target_contact_number(num): 89 | target_contact_number.phone_number = num 90 | 91 | 92 | async def target_info(target): 93 | page, _ = await browser_config.configure_browser_and_load_whatsapp() 94 | 95 | if target is not None: 96 | try: 97 | await target_search.search_and_select_target(page, target) 98 | except Exception as e: 99 | print(e) 100 | await page.reload() 101 | await target_search.search_and_select_target_without_new_chat_button(page, target) 102 | else: 103 | await target_select.manual_select_target(page) 104 | """ 105 | # to find location by ip address 106 | print('Get you ipinfo token from https://ipinfo.io/account') 107 | ip_address = '*' 108 | token = str(input("Enter your ipinfo token: ")) 109 | ip_string = 'curl ipinfo.io/'+ip_address+'?token='+token+'' 110 | os.system(ip_string) 111 | """ 112 | __logger.info("Writing target's information") 113 | scanNumber(target_contact_number.phone_number) 114 | -------------------------------------------------------------------------------- /wplay/download_media.py: -------------------------------------------------------------------------------- 1 | # region Imports 2 | from pathlib import Path 3 | 4 | from wplay.utils import browser_config 5 | from wplay.utils import target_search 6 | from wplay.utils import target_select 7 | from wplay.utils.helpers import media_path 8 | from wplay.utils.Logger import Logger 9 | from wplay.utils.helpers import whatsapp_selectors_dict 10 | import time 11 | # endregion 12 | 13 | 14 | # region LOGGER 15 | __logger = Logger(Path(__file__).name) 16 | # endregion 17 | 18 | 19 | async def download_media(target): 20 | page, browser = await browser_config.configure_browser_and_load_whatsapp() 21 | 22 | if target is not None: 23 | try: 24 | await target_search.search_and_select_target(page, target) 25 | except Exception as e: 26 | print(e) 27 | await page.reload() 28 | await target_search.search_and_select_target_without_new_chat_button( 29 | page, 30 | target 31 | ) 32 | else: 33 | await target_select.manual_select_target(page) 34 | 35 | count = int(input("Count of media you want to download: ")) 36 | 37 | # Click on the photo element of the target 38 | await page.waitForSelector(whatsapp_selectors_dict['target_name_selector'], visible=True) 39 | await page.evaluate(f'''document.querySelector('{whatsapp_selectors_dict['target_name_selector']}').click()''') 40 | 41 | time.sleep(1) 42 | 43 | # Click on the `Media, Link and Docs` text 44 | await page.waitForSelector(whatsapp_selectors_dict['media_text']) 45 | await page.click(whatsapp_selectors_dict['media_text']) 46 | 47 | # Click on the most recent media element 48 | while True: 49 | try: 50 | await page.evaluate(f'''document.querySelector('{whatsapp_selectors_dict['media_images']}').click()''') 51 | break 52 | except Exception as e: 53 | print("", end='') 54 | 55 | media_arr = {'img': [], 'vid': []} 56 | 57 | # Currently downloads the last 50 medias 58 | for _ in range(count): 59 | try: 60 | try: 61 | # If media is an image 62 | countTry = 0 # Threshold of how many times to try looking for media 63 | while True: 64 | if countTry > 500: 65 | await page.waitForSelector(whatsapp_selectors_dict['left_arrow_button']) 66 | 67 | img = await page.evaluate(f'''() => [...document.querySelectorAll('{whatsapp_selectors_dict['media_url_img']}')] 68 | .map(element => element.src)''') 69 | if img and len(img) == 2: 70 | img = img[-1] 71 | if img not in media_arr: 72 | media_arr['img'].append(img) 73 | break 74 | countTry += 1 75 | time.sleep(0.3) 76 | except Exception as e: 77 | # If media is a video or gif 78 | countTry = 0 79 | while True: 80 | vid = await page.evaluate(f'''() => [...document.querySelectorAll('{whatsapp_selectors_dict['media_url_vid']}')] 81 | .map(element => element.src)''') 82 | if vid: 83 | vid = vid[-1] 84 | media_arr['vid'].append(vid) 85 | break 86 | 87 | # Go to next media element 88 | await page.waitForSelector(whatsapp_selectors_dict['left_arrow_button']) 89 | await page.evaluate(f'''document.querySelector('{whatsapp_selectors_dict['left_arrow_button']}').click()''') 90 | time.sleep(0.5) 91 | 92 | except Exception as e: 93 | print(e) 94 | 95 | count = 0 96 | newPage = await browser.newPage() 97 | # Downloading media 98 | for image in media_arr['img']: 99 | try: 100 | count += 1 101 | print(image) 102 | viewSource = await newPage.goto(image) 103 | f = open(media_path / f'{count}.jpg', 'wb') 104 | f.write(await viewSource.buffer()) 105 | f.close() 106 | except Exception as e: 107 | print("Error saving image", e) 108 | 109 | for video in media_arr['vid']: 110 | try: 111 | count += 1 112 | viewSource = await newPage.goto(video) 113 | f = open(media_path / f'{count}.mp4', 'wb') 114 | f.write(await viewSource.buffer()) 115 | f.close() 116 | except Exception as e: 117 | print("Error saving video", e) 118 | 119 | print("Saved the media to the 'media' dir") 120 | time.sleep(10) 121 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
3 |
4 | # whatsapp-play
5 |
6 | [](https://pepy.tech/project/wplay)
7 | [](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)
8 | [](https://pypi.org/project/wplay/)
9 | 
10 | [](https://hub.docker.com/repository/docker/rpotter12/whatsapp-play/general)
11 | [](https://travis-ci.org/rpotter12/whatsapp-play)
12 | [](https://codecov.io/gh/rpotter12/whatsapp-play)
13 | [](https://twitter.com/rpotter121998)
14 | [](http://hits.dwyl.io/rpotter12/whatsapp-play)
15 | [](https://gitter.im/whatsapp-play/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
16 | [](https://gitpod.io/#https://github.com/rpotter12/whatsapp-play)
17 |
18 | 