From 60aa7b77852d1754d4eb1069a5f8902e8a0e1079 Mon Sep 17 00:00:00 2001 From: BassamMutairi Date: Sat, 29 Oct 2022 14:14:32 +0300 Subject: [PATCH 1/2] snanshot changes: - create feature directory which contains sub-directories based on social feature - create project entry point and named it yocial from Yazeed & Social authored-by: Bassam --- .gitignore | 165 ++++++++++++++++++ Main.py => yocial/Main.py | 17 +- Utils.py => yocial/Utils.py | 2 +- yocial/__init__.py | 4 + yocial/__main__.py | 9 + .../features/BaseSocialApp.py | 0 .../features/Instagram/Instagram.py | 4 +- .../features/PhoneNumber.py | 0 .../features/SocialDetector.py | 0 .../features/Telegram/Telegram.py | 0 .../features/Whatsapp/Whatsapp.py | 0 .../features/Whatsapp/whatsapp_detect.go | 0 12 files changed, 189 insertions(+), 12 deletions(-) create mode 100644 .gitignore rename Main.py => yocial/Main.py (52%) rename Utils.py => yocial/Utils.py (94%) create mode 100644 yocial/__init__.py create mode 100644 yocial/__main__.py rename BaseSocialApp.py => yocial/features/BaseSocialApp.py (100%) rename Instagram.py => yocial/features/Instagram/Instagram.py (97%) rename PhoneNumber.py => yocial/features/PhoneNumber.py (100%) rename SocialDetector.py => yocial/features/SocialDetector.py (100%) rename Telegram.py => yocial/features/Telegram/Telegram.py (100%) rename Whatsapp.py => yocial/features/Whatsapp/Whatsapp.py (100%) rename whatsapp_detect.go => yocial/features/Whatsapp/whatsapp_detect.go (100%) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1aad824 --- /dev/null +++ b/.gitignore @@ -0,0 +1,165 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# vscode +.vscode/ +.idea/ + + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +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 +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# 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 +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .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 + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# 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/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ \ No newline at end of file diff --git a/Main.py b/yocial/Main.py similarity index 52% rename from Main.py rename to yocial/Main.py index 50320ff..102f570 100644 --- a/Main.py +++ b/yocial/Main.py @@ -4,20 +4,19 @@ # Ultimately, I want Main.py to only have command arguments processing. Will work on that later # Right now Main.py is used to demonstrate how to use the API import sys - -import Utils -import private_constants -from Instagram import Instagram -from SocialDetector import SocialDetector -from Telegram import Telegram -from Whatsapp import Whatsapp +import yocial.Utils as Utils +#import private_constants +from yocial.features.Instagram.Instagram import Instagram +from yocial.features.SocialDetector import SocialDetector +#from feature.Telegram.Telegram import Telegram +#from feature.Whatsapp.Whatsapp import Whatsapp def main(): phone_number_list = Utils.cmd_args_to_phone_number(sys.argv) detector = SocialDetector() - detector.add_social_app(Telegram(phone_number_list, private_constants.TELEGRAM_API_ID, private_constants.TELEGRAM_API_HASH)) - detector.add_social_app(Whatsapp(phone_number_list)) + #detector.add_social_app(Telegram(phone_number_list, private_constants.TELEGRAM_API_ID, private_constants.TELEGRAM_API_HASH)) + #detector.add_social_app(Whatsapp(phone_number_list)) detector.add_social_app(Instagram(phone_number_list)) detector.detect() for number in phone_number_list: diff --git a/Utils.py b/yocial/Utils.py similarity index 94% rename from Utils.py rename to yocial/Utils.py index 908ed82..b2bf4d1 100644 --- a/Utils.py +++ b/yocial/Utils.py @@ -1,6 +1,6 @@ import os -from PhoneNumber import PhoneNumber +from yocial.features.PhoneNumber import PhoneNumber def file_to_phone_number(file_path): diff --git a/yocial/__init__.py b/yocial/__init__.py new file mode 100644 index 0000000..1cb91d4 --- /dev/null +++ b/yocial/__init__.py @@ -0,0 +1,4 @@ +from os.path import dirname, basename, isfile, join +import glob +modules = glob.glob(join(dirname(__file__), "*.py")) +__all__ = [ basename(f)[:-3] for f in modules if isfile(f) and not f.endswith('__init__.py')] \ No newline at end of file diff --git a/yocial/__main__.py b/yocial/__main__.py new file mode 100644 index 0000000..6414962 --- /dev/null +++ b/yocial/__main__.py @@ -0,0 +1,9 @@ + + +''' +Entry point to run yocial module `python3 -m yocial`. +''' + +from yocial.Main import main + +main() diff --git a/BaseSocialApp.py b/yocial/features/BaseSocialApp.py similarity index 100% rename from BaseSocialApp.py rename to yocial/features/BaseSocialApp.py diff --git a/Instagram.py b/yocial/features/Instagram/Instagram.py similarity index 97% rename from Instagram.py rename to yocial/features/Instagram/Instagram.py index 52c554e..191c436 100644 --- a/Instagram.py +++ b/yocial/features/Instagram/Instagram.py @@ -7,8 +7,8 @@ import requests -import PhoneNumber -from BaseSocialApp import BaseSocialApp +import yocial.features.PhoneNumber as PhoneNumber +from yocial.features.BaseSocialApp import BaseSocialApp API_URL = 'https://i.instagram.com/api/v1/' USERS_LOOKUP_URL = API_URL + 'users/lookup/' diff --git a/PhoneNumber.py b/yocial/features/PhoneNumber.py similarity index 100% rename from PhoneNumber.py rename to yocial/features/PhoneNumber.py diff --git a/SocialDetector.py b/yocial/features/SocialDetector.py similarity index 100% rename from SocialDetector.py rename to yocial/features/SocialDetector.py diff --git a/Telegram.py b/yocial/features/Telegram/Telegram.py similarity index 100% rename from Telegram.py rename to yocial/features/Telegram/Telegram.py diff --git a/Whatsapp.py b/yocial/features/Whatsapp/Whatsapp.py similarity index 100% rename from Whatsapp.py rename to yocial/features/Whatsapp/Whatsapp.py diff --git a/whatsapp_detect.go b/yocial/features/Whatsapp/whatsapp_detect.go similarity index 100% rename from whatsapp_detect.go rename to yocial/features/Whatsapp/whatsapp_detect.go From ede6e3b8d5d5c2a13d06d8f2be7d80778e76d9b7 Mon Sep 17 00:00:00 2001 From: BassamMutairi Date: Sat, 29 Oct 2022 23:06:42 +0300 Subject: [PATCH 2/2] - add PhoneNumber verification base logic - migrate PhoneNumber to support dataclass and encapsulation - generic error handler - uncomment original code --- yocial/Main.py | 10 +++--- yocial/Utils.py | 13 +++++--- yocial/features/Instagram/Instagram.py | 6 +++- yocial/features/PhoneNumber.py | 44 ++++++++++++++++++++------ 4 files changed, 53 insertions(+), 20 deletions(-) diff --git a/yocial/Main.py b/yocial/Main.py index 102f570..57a205a 100644 --- a/yocial/Main.py +++ b/yocial/Main.py @@ -5,18 +5,18 @@ # Right now Main.py is used to demonstrate how to use the API import sys import yocial.Utils as Utils -#import private_constants +import private_constants from yocial.features.Instagram.Instagram import Instagram from yocial.features.SocialDetector import SocialDetector -#from feature.Telegram.Telegram import Telegram -#from feature.Whatsapp.Whatsapp import Whatsapp +from yocial.features.Telegram.Telegram import Telegram +from yocial.features.Whatsapp.Whatsapp import Whatsapp def main(): phone_number_list = Utils.cmd_args_to_phone_number(sys.argv) detector = SocialDetector() - #detector.add_social_app(Telegram(phone_number_list, private_constants.TELEGRAM_API_ID, private_constants.TELEGRAM_API_HASH)) - #detector.add_social_app(Whatsapp(phone_number_list)) + detector.add_social_app(Telegram(phone_number_list, private_constants.TELEGRAM_API_ID, private_constants.TELEGRAM_API_HASH)) + detector.add_social_app(Whatsapp(phone_number_list)) detector.add_social_app(Instagram(phone_number_list)) detector.detect() for number in phone_number_list: diff --git a/yocial/Utils.py b/yocial/Utils.py index b2bf4d1..0cf1e8c 100644 --- a/yocial/Utils.py +++ b/yocial/Utils.py @@ -10,19 +10,24 @@ def file_to_phone_number(file_path): phone_number_list.append(PhoneNumber(line.strip('\n'))) return phone_number_list - # Convert command line arguments to PhoneNumber objects. If the arguments is a file's name then that file will be # processed and not the arguments themselves def cmd_args_to_phone_number(arguments): + # Currently the processing of numbers to objects is sensitive. Meaning that empty and invalid lines might get # processed into an objects, which obviously shouldn't. Bear that in mind - assert len(arguments) >= 1, "No arguments are provided" - + # as a bypass for the time being until I develop verify function I will incrment the condition to 2 + # as arguments[0] is always the project entry point `__main__.py` + assert len(arguments) >= 2, "No arguments are provided" + phone_number_list = [] if os.path.isfile(arguments[1]): return file_to_phone_number(arguments[1]) else: for raw_phone_number in arguments[1:]: - phone_number_list.append(PhoneNumber(raw_phone_number)) + # ensure PhoneNumber object initialize only for valid numbers + verified = PhoneNumber.verifier(number=raw_phone_number) + if(verified): + phone_number_list.append(PhoneNumber(raw_phone_number)) return phone_number_list diff --git a/yocial/features/Instagram/Instagram.py b/yocial/features/Instagram/Instagram.py index 191c436..ba38a82 100644 --- a/yocial/features/Instagram/Instagram.py +++ b/yocial/features/Instagram/Instagram.py @@ -99,7 +99,7 @@ def generate_data(self, phone_number_raw): def detect_single_number(self, phone_number): self.setup_session() - data = self.generate_data(phone_number.get_phone_number()) + data = self.generate_data(phone_number.phone_number) response = self.current_session.post(USERS_LOOKUP_URL, data=generate_signature(json.dumps(data))) self.last_response = response @@ -113,6 +113,10 @@ def detect_single_number(self, phone_number): phone_number.set_app_state("Whatsapp", PhoneNumber.AppUsageEnum.NO_USAGE) elif response.status_code == 404: phone_number.set_app_state(self.get_name(), PhoneNumber.AppUsageEnum.NO_USAGE) + else: + # a generic bypass for generic failures for unexpected errors + # for my case I faced `Too many requests` error + phone_number.set_app_state(self.get_name(), response.reason) def detect_numbers(self, phone_numbers): for phone in phone_numbers: diff --git a/yocial/features/PhoneNumber.py b/yocial/features/PhoneNumber.py index ca2bf23..e0c0a32 100644 --- a/yocial/features/PhoneNumber.py +++ b/yocial/features/PhoneNumber.py @@ -1,22 +1,26 @@ from enum import Enum - - +from dataclasses import dataclass, field class AppUsageEnum(Enum): ERROR = -1 # There was an error while trying to detect whether this phone number uses a certain social app. NO_USAGE = 0 # This phone number does not use the app USAGE = 1 # This phone number has the app and it uses it - +@dataclass class PhoneNumber: + _phone_number: str = "" + # Used to track which apps are used by this phone number + app_usage: dict = field(init=False,default_factory=dict) - # TODO include the usernames of each application in here - def __init__(self, phone_number): - self.phone_number = phone_number - self.app_usage = {} # Used to track which apps are used by this phone number + # get a current phone number + @property + def phone_number(self) -> str: + return self._phone_number - def get_phone_number(self): - return self.phone_number + # set a new phone number + @phone_number.setter + def phone_number(self, value: str) -> None: + self._phone_number = value # Return a constant of AppUsageEnum def has_app(self, app_name): @@ -30,5 +34,25 @@ def set_app_state(self, app_name, state): def __str__(self): usage_as_string = self.phone_number + "\n" for key, val in self.app_usage.items(): - usage_as_string += key + " => " + val.name + "\n" + # to handle errors + try: + usage_as_string += key + " => " + val.name + "\n" + except: + usage_as_string += key + " => " + val + "\n" return usage_as_string + + def verifier(number) -> bool: + ''' + Return boolean value based on the verification logic + + Parameters: + number -> the number need to be verified + Return: + True -> if it's valid + False -> if it's not valid + ''' + # TODO: substring condition + if(len(number) == 10): + return True + else: + return False \ No newline at end of file