OSINT tool that allows to gather information from an email address, an username, and more !
import philINT.utils as utils

class Email:
    """
    An Email address with :
        connections     : information gathered from websites from the email address
        is_pwned        : was the email in a data leak/breach - Requires an HIBP API key that you can buy here : https://haveibeenpwned.com/API/Key
        is_spam         : is this considered as a spam email address
        is_deliverable  : can it receive an email
        is_disposable   : is it a temporary email address
    """
    def __init__(self, email_address:str):
        self.email_address = email_address
        self.connections = None
        self.is_pwned = None
        self.is_spam = None
        self.is_deliverable = None
        self.is_disposable = None

    def __iter__(self):
        for attr, value in self.__dict__.items():
            yield attr, value

    def display(self):
        print("Email address : " + self.email_address)
        for field, value in self:
            if value is not None and field not in ["email_address", "connections"]:
                print("\t • " + field[3:].capitalize() + " : " + str(value))

    async def make_connections_coro(self):
        """
        Coroutine to get information from an email address from "common" websites.
        Works with jupyter notebooks.
        """
        self.connections = await utils.get_data_from_email_coro(self.email_address)

    def make_connections(self):
        """
        Function to get information from an email address from "common" websites.
        It doesn't work with jupyter notebooks.
        """
        self.connections = utils.get_data_from_email(self.email_address)
    
    async def get_additionnal_info_eva_coro(self):
        """
        Adds data from Email Verification API
        Works with jupyter notebooks.
        """
        result = await utils.get_additionnal_info_email_eva_coro(self.email_address)
        self.is_spam = result[0]
        self.is_deliverable = result[1]
        self.is_disposable = result[2]
    
    def get_additionnal_info_eva(self):
        """
        Adds data from Email Verification API
        Doesn't work with jupyter notebooks.
        """
        result = utils.get_additionnal_info_email_eva(self.email_address)
        self.is_spam = result[0]
        self.is_deliverable = result[1]
        self.is_disposable = result[2]

    async def run_all_coro(self):
        """
        Gets data from every source available on the tool for an email address
        Works with jupyter notebooks.
        """
        await self.get_additionnal_info_eva_coro()
        await self.make_connections_coro()
    
    def run_all(self):
        """
        Gets data from every source available on the tool for an email address
        Doesn't work with jupyter notebooks.
        """
        self.get_additionnal_info_eva()
        self.make_connections()

class Username:
    """
    An Username with :
        connections     : information gathered from websites from the email address
    """
    def __init__(self, username:str):
        self.username = username
        self.connections = None
    
    async def make_connections_coro(self):
        """
        Coroutine to get information from an username from "common" websites.
        Works with jupyter notebooks.
        """
        self.connections = await utils.get_data_from_username_coro(self.username)

    def make_connections(self):
        """
        Function to get information from an username from "common" websites.
        It doesn't work with jupyter notebooks.
        """
        self.connections = utils.get_data_from_username(self.username)

class Person:
    """
    A Person is supposed to be a real-life human. The object can be instantiated then "filled" with data from an email address or an username.
    It is designed to help investigators by removing redundant info, but still contains all the data for more in-depth searching.
    It stores specifically :
        email_addresses
        usernames
        names
        phone_numbers
        pictures
        accounts
        all_data
    """
    def __init__(self):
        self.email_addresses = []
        self.usernames = []
        self.names = []
        self.phone_numbers = []
        self.pictures = []
        self.accounts = []
        self.all_data = []
    
    def __iter__(self):
        for attr, value in self.__dict__.items():
            yield attr, value
    
    def fill_from_email(self, email:Email):
        """
        Fills a Person object with data from an Email object
        """
        self.email_addresses = list(set(self.email_addresses + [email.email_address] + [connection.Emails for connection in email.connections if "Mails" in connection._fields and connection.Mails is not None]))
        self.usernames = list(set(self.usernames + [connection.Username for connection in email.connections if "Username" in connection._fields and connection.Username is not None]))
        self.names = list(set(self.names + [connection.Name for connection in email.connections if "Name" in connection._fields and connection.Name is not None]))
        self.phone_numbers = list(set(self.phone_numbers + [connection.Phone_number for connection in email.connections if "Phone_number" in connection._fields and connection.Phone_number is not None ]))
        self.all_data = utils.remove_duplicate_unhashable(self.all_data + email.connections)
        self.accounts = list(set(self.accounts + ["Chess.com" if type(el).__name__ == "Chess" else type(el).__name__ for el in email.connections]))
        pics = []
        for connection in email.connections:
            if "Picture" in connection._fields and connection.Picture is not None:
                pics.append(connection.Picture)
            if "Pictures" in connection._fields and connection.Pictures is not None:
                pics += connection.Pictures
        self.pictures = list(set(self.pictures + pics))

    def fill_from_username(self, username:Username):
        """
        Fills a Person object with data from an Username object
        """
        emails = self.email_addresses
        for connection in username.connections:
            if "Emails" in connection._fields:
                for email in connection.Emails:
                    if email is not None:
                        emails.append(email)
        self.email_addresses = list(set(emails))
        self.usernames = list(set(self.usernames + [username.username] + [connection.Username for connection in username.connections if "Username" in connection._fields and connection.Username is not None]))
        self.names = list(set(self.names + [connection.Name for connection in username.connections if "Name" in connection._fields and connection.Name is not None]))
        self.phone_numbers = list(set(self.phone_numbers + [connection.Phone_number for connection in username.connections if "Phone_number" in connection._fields and connection.Phone_number is not None ]))
        self.all_data = utils.remove_duplicate_unhashable(self.all_data + username.connections)
        self.accounts = list(set(self.accounts + ["Chess.com" if type(el).__name__ == "Chess" else type(el).__name__ for el in username.connections]))
        pics = []
        for connection in username.connections:
            if "Picture" in connection._fields and connection.Picture is not None:
                pics.append(connection.Picture)
            if "Pictures" in connection._fields and connection.Pictures is not None:
                pics += connection.Pictures
        self.pictures = list(set(self.pictures + pics))

    def display(self):
        """
        Displays a Person object in a prettier way
        """
        for k, v in self:
            if v != [] and k != "all_data":
                print(k.replace("_", " ").capitalize() + " :")
                for el in v:
                    if type(el) is str:
                        print("\t • " + el)
    
    def export_raw_data(self, file_name:str="data_export"):
        """
        Saves the all_data field to a .json file.
        Default file name is "data_export_[timestamp]"
        """
        utils.all_data_export(self.all_data, file_name)

    def display_raw_data(self):
        """
        Displays the all_data field in a nicer way
        """
        utils.all_data_display(self.all_data)
philINT/classes.py
ajuelosemmanuel/philINT
import argparse
import philINT.classes as cl
import philINT.utils as utils


def main():
    parser = argparse.ArgumentParser(
        prog = "philINT",
        description = "philINT allows to retrieve information from an email address or an username"
    )
    group = parser.add_mutually_exclusive_group(required=True)
    group.add_argument('-e', '--email', help="Target email address", required=False)
    group.add_argument('-u', '--username', help="Target username", required=False)
    parser.add_argument("-x", "--extract", help="Extracts the raw data to a json file", action = "store_true", dest = "output")
    args = parser.parse_args()
    target_person = cl.Person()
    if args.email is not None:
        if not utils.is_email(args.email):
            print("Email address isn't valid")
            exit(0)
        target_email = cl.Email(email_address = args.email)
        target_email.run_all()
        target_person.fill_from_email(target_email)
        utils.print_results_banner()
        target_email.display()
    else:
        target_username = cl.Username(username = args.username)
        target_username.make_connections()
        target_person.fill_from_username(target_username)
        utils.print_results_banner()
    target_person.display()
    if args.output:
        target_person.export_raw_data()
    else:
        print("-------------------------------------")
        target_person.display_raw_data()
philINT/main.py
ajuelosemmanuel/philINT
import httpx, configparser, hashlib
from collections import namedtuple

endpoint_config = configparser.ConfigParser()
endpoint_config.read('philINT/endpoints.conf')

GravatarData = namedtuple("Gravatar", ["Profile", "Username", "Picture", "Pictures", "Name", "Display_name", "About_me", "Accounts", "Currency", "URLs"])
LinkedAccount = namedtuple("Gravatar_Linked_Account", ["Domain", "Display_name", "URL", "Username", "Website_name"])
Currency = namedtuple("Gravatar_Currency", ["Type", "Wallet"])

async def from_email(email):
    email_md5 = hashlib.md5(email.lower().encode('utf-8')).hexdigest()
    async with httpx.AsyncClient() as client:
        resp = await client.get(endpoint_config["Gravatar"]["ENDPOINT_EMAIL"] + email_md5 + ".json", headers={"User-Agent": endpoint_config["BASE"]["USER_AGENT"]})
    response = resp.json()
    if "User not found" in str(response):
        return None
    usercount = len(response["entry"])
    match usercount:
        case 0:
            return None
        case 1:
            print("Gravatar : found 1 user.")
        case _:
            print("Gravatar : found multiple (" + str(usercount) + ") users.")
    return [
        GravatarData(
            Profile = el["profileUrl"],
            Username = el["preferredUsername"],
            Picture = el["thumbnailUrl"],
            Pictures = [photo["value"] for photo in el["photos"]],
            Name = el["name"]["formatted"] if "name" in el else None,
            Display_name = el["displayName"],
            About_me = el["aboutMe"] if "aboutMe" in el else None,
            Accounts = [
                LinkedAccount(
                    Domain = account['domain'],
                    Display_name = account["display"],
                    URL = account["url"],
                    Username = account["username"],
                    Website_name = account["name"]
                ) for account in el["accounts"]
            ] if "accounts" in el else None,
            Currency = [
                Currency(
                    Type = curr["type"],
                    Wallet = curr["value"]
                ) for curr in el["currency"]
            ] if "currency" in el else None,
            URLs = [url["value"] for url in el["urls"]] if len(el["urls"]) != 0 else None
        )
        for el in response["entry"]
    ]
philINT/modules/cms/gravatar.py
ajuelosemmanuel/philINT
import httpx, configparser
from collections import namedtuple

endpoint_config = configparser.ConfigParser()
endpoint_config.read('philINT/endpoints.conf')

WordPressData = namedtuple("WordPress", ["Has_account"])

async def from_email(email):
    async with httpx.AsyncClient() as client:
        resp = await client.get(endpoint_config["WordPress"]["ENDPOINT_EMAIL"] + email + "/auth-options", headers={"User-Agent": endpoint_config["BASE"]["USER_AGENT"]})
    response = resp.json()
    if "message" in response and response["message"] == "User does not exist.":
        return None
    print("WordPress : found 1 user.")
    return WordPressData(Has_account = True)
philINT/modules/cms/wordpress.py
ajuelosemmanuel/philINT
import httpx, configparser, datetime, pycountry
from iso639 import languages
from collections import namedtuple

endpoint_config = configparser.ConfigParser()
endpoint_config.read('philINT/endpoints.conf')

DuolingoData = namedtuple("Duolingo", ["Username", "Picture", "Name", "Account_creation_date", "Country", "Streak", "Has_recent_activity", "Bio", "Languages_studied"])
Course = namedtuple("Duolingo_Course", ["Language", "Learning_from", "Crowns", "Completion", "Exp"])

def duolingo_response_parser(response):
    usercount = len(response["users"])
    match usercount:
        case 0:
            return None
        case 1:
            print("Duolingo : found 1 user.")
        case _:
            print("Duolingo : found multiple (" + str(usercount) + ") users.")
    results = [
        DuolingoData(
        Username = user["username"],
        Picture = "https:" + user["picture"] + "/xxlarge" if "default" not in user["picture"] else None,
        Name = user["name"] if "name" in user else None,
        Account_creation_date = str(datetime.datetime.fromtimestamp(user["creationDate"])),
        Country = pycountry.countries.get(alpha_2=user["profileCountry"]).name if user["profileCountry"] is not None else None,
        Streak = str(user["streak"]),
        Has_recent_activity = str(user["hasRecentActivity15"]),
        Bio = user["bio"] if user["bio"] != "" else None,
        Languages_studied = [
            Course(
                Language = course["title"],
                Learning_from = languages.get(alpha2=course["fromLanguage"]).name,
                Crowns = course["crowns"],
                Completion = str(round(100 * course["crowns"] / int(CROWN_DICT[course["title"] + "_" + languages.get(alpha2=course["fromLanguage"]).name]), 2)) + " %",
                Exp = course["xp"]
            )
            for course in user["courses"]
        ]
        )
        for user in response["users"]
    ]
    return results

async def from_email(email):
    async with httpx.AsyncClient() as client:
        response = await client.get(endpoint_config["Duolingo"]["ENDPOINT_EMAIL"] + email, headers={"User-Agent": endpoint_config["BASE"]["USER_AGENT"]})
    return duolingo_response_parser(response.json())

async def from_username(username):
    async with httpx.AsyncClient() as client:
        response = await client.get(endpoint_config["Duolingo"]["ENDPOINT_USERNAME"] + username, headers={"User-Agent": endpoint_config["BASE"]["USER_AGENT"]})
    return duolingo_response_parser(response.json())

CROWN_DICT = {'Arabic_English': '276',
 'Chinese_English': '528',
 'Czech_English': '504',
 'Danish_English': '420',
 'Dutch_English': '738',
 'Esperanto_English': '414',
 'Finnish_English': '210',
 'French_English': '1406',
 'German_English': '826',
 'Greek_English': '570',
 'Haitian Creole_English': '216',
 'Hawaiian_English': '228',
 'Hebrew_English': '504',
 'High Valyrian_English': '258',
 'Hindi_English': '192',
 'Hungarian_English': '468',
 'Indonesian_English': '414',
 'Irish_English': '384',
 'Italian_English': '396',
 'Japanese_English': '655',
 'Klingon_English': '390',
 'Korean_English': '564',
 'Latin_English': '132',
 'Navajo_English': '66',
 'Norwegian_English': '1032',
 'Polish_English': '402',
 'Portuguese_English': '546',
 'Romanian_English': '372',
 'Russian_English': '474',
 'Scottish Gaelic_English': '642',
 'Spanish_English': '1521',
 'Swahili_English': '390',
 'Swedish_English': '396',
 'Turkish_English': '402',
 'Ukrainian_English': '306',
 'Vietnamese_English': '504',
 'Welsh_English': '642',
 'Yiddish_English': '564',
 'Zulu_English': '120',
 'Catalan_Spanish': '378',
 'English_Spanish': '1439',
 'Esperanto_Spanish': '372',
 'French_Spanish': '408',
 'German_Spanish': '432',
 'Guarani_Spanish': '330',
 'Italian_Spanish': '396',
 'Portuguese_Spanish': '414',
 'Russian_Spanish': '420',
 'Swedish_Spanish': '684',
 'Cantonese_Chinese': '108',
 'English_Chinese': '882',
 'French_Chinese': '654',
 'Japanese_Chinese': '666',
 'Korean_Chinese': '894',
 'Italian_Chinese': '396',
 'Spanish_Chinese': '384',
 'English_French': '330',
 'Esperanto_French': '414',
 'German_French': '702',
 'Italian_French': '600',
 'Portuguese_French': '414',
 'Spanish_French': '456',
 'English_Portuguese': '1321',
 'Esperanto_Portuguese': '372',
 'French_Portuguese': '420',
 'German_Portuguese': '438',
 'Italian_Portuguese': '408',
 'Spanish_Portuguese': '384',
 'English_Arabic': '366',
 'French_Arabic': '468',
 'German_Arabic': '720',
 'Swedish_Arabic': '396',
 'English_German': '380',
 'French_German': '774',
 'Italian_German': '180',
 'Spanish_German': '384',
 'English_Italian': '372',
 'French_Italian': '468',
 'German_Italian': '432',
 'Spanish_Italian': '384',
 'Chinese_Japanese': '360',
 'English_Japanese': '816',
 'French_Japanese': '360',
 'Korean_Japanese': '360',
 'English_Russian': '774',
 'French_Russian': '468',
 'German_Russian': '666',
 'Spanish_Russian': '384',
 'English_Dutch': '330',
 'French_Dutch': '360',
 'German_Dutch': '468',
 'English_Turkish': '330',
 'German_Turkish': '432',
 'Russian_Turkish': '318',
 'English_Hungarian': '330',
 'German_Hungarian': '306',
 'Chinese_Vietnamese': '360',
 'English_Vietnamese': '630',
 'English_Bengali': '120',
 'English_Czech': '330',
 'English_Greek': '330',
 'English_Hindi': '324',
 'English_Indonesian': '275',
 'English_Korean': '522',
 'English_Polish': '330',
 'English_Romanian': '330',
 'English_Thai': '354',
 'English_Tagalog': '276',
 'English_Ukrainian': '330',
 'Welsh_English': '708'}
philINT/modules/education/duolingo.py
ajuelosemmanuel/philINT
import httpx, configparser, datetime, time
from collections import namedtuple
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.options import Options

endpoint_config = configparser.ConfigParser()
endpoint_config.read('philINT/endpoints.conf')

credentials_config = configparser.ConfigParser()
credentials_config.read('philINT/credentials.conf')

ChessData = namedtuple("Chess", ["Picture", "Profile", "Name", "Username", "Country", "Location", "Last_online", "Account_creation_date"])

async def from_email(email):
    username = credentials_config["Chess"]["USERNAME"]
    password = credentials_config["Chess"]["PASSWORD"]
    if username == '' or password == '':
        return None
    options = Options()
    options.add_argument('--headless=new')
    options.add_experimental_option('excludeSwitches', ['enable-logging'])
    driver = webdriver.Chrome(options=options)
    driver.get("https://www.chess.com/login")
    driver.find_element("id", "username").send_keys(username)
    driver.find_element("id", "password").send_keys(password)
    driver.find_element("id", "login").click()
    WebDriverWait(driver=driver, timeout=10).until(
        lambda x: x.execute_script("return document.readyState === 'complete'")
    )
    driver.get("https://www.chess.com/friends?name=" + email)
    time.sleep(0.18)
    try :
        driver.find_element(By.CLASS_NAME, "friends-section-suggestions")
    except NoSuchElementException :
        return None
    user_data = driver.find_element(By.CLASS_NAME, "users-list-item")
    user_data.get_attribute("innerHTML")
    return await from_username(user_data.get_attribute("innerHTML").split("username")[1].split("\"")[1])

async def from_username(username):
    async with httpx.AsyncClient() as client:
        resp = await client.get(endpoint_config["Chess"]["ENDPOINT_USERNAME"] + username.lower(), headers={"User-Agent": endpoint_config["BASE"]["USER_AGENT"]})
    if 'not found' in resp.text or resp.text == "":
        return None
    print("Chess.com : found 1 user.")
    response = resp.json()
    return ChessData(
        Picture = response["avatar"] if "avatar" in response else None,
        Profile = response["url"],
        Name = response["name"] if "name" in response else None,
        Username = response["username"],
        Country = response["country"],
        Location = response["location"] if "location" in response else None,
        Last_online = datetime.datetime.utcfromtimestamp(response["last_online"]).strftime('%Y-%m-%d %H:%M:%S'),
        Account_creation_date = datetime.datetime.utcfromtimestamp(response["joined"]).strftime('%Y-%m-%d %H:%M:%S')
    )
philINT/modules/games/chess.py
ajuelosemmanuel/philINT
import httpx, configparser, philINT.utils
from collections import namedtuple

endpoint_config = configparser.ConfigParser()
endpoint_config.read('philINT/endpoints.conf')

GitHubData = namedtuple("GitHub", ["Username", "Github_profile", "Github_gists_profile", "Picture", "Pictures", "Links", "Emails", "Gravatar_id", "Company", "Location", "Twitter_username", "Created_at", "Updated_at"])

async def from_email(email):
    async with httpx.AsyncClient() as client:
        resp = await client.get(endpoint_config["GitHub"]["ENDPOINT_EMAIL"] + email)
    response = resp.json()
    if response["total_count"] == 1:
        return await from_username(response["items"][0]["login"])
    elif response["total_count"] == 0:
        return None
    else:
        print("GitHub : Multiple users display the same email address, can't pivot accurately")
        return None

async def username_to_readme(username):
    async with httpx.AsyncClient() as client:
        resp = await client.get(endpoint_config["GitHub"]["ENDPOINT_USERNAME"] + username + "/repos")
    repos = resp.json()
    if repos != []:
        for el in repos:
            if el["url"].endswith(username):
                async with httpx.AsyncClient() as client:
                    return await client.get("https://raw.githubusercontent.com/" + username + "/" + username + "/master/README.md").text
    return None

async def from_username(username):
    async with httpx.AsyncClient() as client:
        resp = await client.get(endpoint_config["GitHub"]["ENDPOINT_USERNAME"] + username)
    user_data = resp.json()
    if "message" in user_data:
        return None
    print("GitHub : found 1 user.")
    readme = await username_to_readme(username)
    if readme is not None :
        readme_data = philINT.utils.extract_links_and_emails(readme)
    else:
        readme_data = None
    return GitHubData(
        Username = user_data["login"],
        Github_profile = user_data["html_url"],
        Github_gists_profile = user_data["html_url"][0:8] + "gists." + user_data["html_url"][8:] if user_data["public_gists"] != 0 else None,
        Picture = user_data["avatar_url"],
        Pictures = readme_data[1] if readme_data is not None and readme_data[1] != [] else None,
        Links = readme_data[0] + [user_data["blog"]] if readme_data is not None and readme_data[0] != [] and user_data["blog"] != [] else [user_data["blog"]] if user_data["blog"] != "" else readme_data[0] if readme_data is not None and readme_data[0] != [] else None,
        Emails = readme_data[2] + [user_data["email"]] if readme_data is not None and readme_data[2] != [] and user_data["email"] != [] else [user_data["email"]] if user_data["email"] != "" else readme_data[2] if readme_data is not None and readme_data[2] != [] else None,
        Gravatar_id = user_data["gravatar_id"] if user_data["gravatar_id"] != "" else None,
        Company = user_data["company"] if user_data["company"] != "" else None,
        Location = user_data["location"] if user_data["location"] != "" else None,
        Twitter_username = user_data["twitter_username"] if user_data["twitter_username"] != "" else None,
        Created_at = user_data["created_at"],
        Updated_at = user_data["updated_at"]
    )

philINT/modules/programming/github.py
ajuelosemmanuel/philINT
import httpx, configparser
from collections import namedtuple

endpoint_config = configparser.ConfigParser()
endpoint_config.read('philINT/endpoints.conf')

ImgurData = namedtuple("Imgur", ["Has_account"])

async def from_email(email):
    headers = {
        "content-type": "application/x-www-form-urlencoded; charset=UTF-8",
        "User-Agent": endpoint_config["BASE"]["USER_AGENT"]
    }
    async with httpx.AsyncClient() as client:
        resp = await client.post(endpoint_config["Imgur"]["ENDPOINT_EMAIL"], headers = headers, data = "email=" + email)
    response = resp.json()
    if "data" in response and response["data"]["available"] == False:
        print("Imgur : found 1 user.")
        return ImgurData(Has_account = True)
    return None
philINT/modules/social_media/imgur.py
ajuelosemmanuel/philINT
import httpx, configparser
from collections import namedtuple

endpoint_config = configparser.ConfigParser()
endpoint_config.read('philINT/endpoints.conf')

MeWeData = namedtuple("MeWe", ["Has_account"])

async def from_email(email):
    headers = {
    'accept': 'application/json, text/plain, */*',
    "User-Agent": endpoint_config["BASE"]["USER_AGENT"]
    }
    async with httpx.AsyncClient() as client:
        resp = await client.get(endpoint_config["MeWe"]["ENDPOINT_EMAIL"] + email, headers = headers)
    response = resp.json()
    if "already taken" in str(response):
        print("MeWe : found 1 user.")
        return MeWeData(Has_account = True)
    return None
philINT/modules/social_media/mewe.py
ajuelosemmanuel/philINT
import httpx, configparser
from collections import namedtuple

endpoint_config = configparser.ConfigParser()
endpoint_config.read('philINT/endpoints.conf')

MyAnimeListData = namedtuple("MyAnimeList", ["Last_online", "Gender", "Date_of_birth", "Location", "Joined_at"], defaults=(None,) * 5)

async def from_username(username):
    async with httpx.AsyncClient() as client:
        response = await client.get(endpoint_config["MyAnimeList"]["ENDPOINT_USERNAME"] + username, headers={"User-Agent": endpoint_config["BASE"]["USER_AGENT"]})
    if response.status_code == 404:
        return None
    print("MyAnimeList : found 1 user.")
    parsed = response.text.split("user-status border-top")[1].split("</ul>")[0].split('<span class="user-status-title di-ib fl-l fw-b">')[1:]
    results = MyAnimeListData()
    for line in parsed:
        parsed_line = line.split('</span><span class="user-status-data di-ib fl-r">')
        match parsed_line[0]:
            case 'Last Online':
                results = results._replace(Last_online=parsed_line[1].split("</span>")[0])
            case 'Gender':
                results = results._replace(Gender=parsed_line[1].split("</span>")[0])
            case 'Birthday':
                results = results._replace(Date_of_birth=parsed_line[1].split("</span>")[0])
            case 'Location':
                results = results._replace(Location=parsed_line[1].split("</span>")[0])
            case 'Joined':
                results = results._replace(Joined_at=parsed_line[1].split("</span>")[0])
    return results
philINT/modules/social_media/myanimelist.py
ajuelosemmanuel/philINT
import httpx, configparser
from collections import namedtuple

endpoint_config = configparser.ConfigParser()
endpoint_config.read('philINT/endpoints.conf')

TwitterData = namedtuple("Twitter", ["Has_account"])

async def from_email(email):
    async with httpx.AsyncClient() as client:
        resp = await client.get(endpoint_config["Twitter"]["ENDPOINT_EMAIL"] + email, headers={"User-Agent": endpoint_config["BASE"]["USER_AGENT"]})
    response = resp.json()
    if response["taken"]:
        print("Twitter : found 1 user.")
        return TwitterData(Has_account = True)
    return None
philINT/modules/social_media/twitter.py
ajuelosemmanuel/philINT
import httpx, configparser
from collections import namedtuple

endpoint_config = configparser.ConfigParser()
endpoint_config.read('philINT/endpoints.conf')

AdobeData = namedtuple("Adobe", ["Linked_to_Google", "Linked_to_Facebook", "is_Active", "Pictures"])

async def from_email(email):
    headers = {"User-Agent": endpoint_config["BASE"]["USER_AGENT"], "content-type": "application/json","x-ims-clientid": "adobedotcom2"}
    json = {"username": email}
    async with httpx.AsyncClient() as client:
        response = await client.post(endpoint_config["Adobe"]["ENDPOINT_EMAIL"], headers = headers, json = json)
    resp = response.json()
    usercount = len(resp)
    match usercount:
        case 0:
            return None
        case 1:
            print("Adobe : found 1 user.")
        case _:
            print("Adobe : found multiple (" + str(usercount) + ") users.")
    results = [
        AdobeData(
        Linked_to_Google = "google" in str(el["authenticationMethods"]),
        Linked_to_Facebook = "facebook" in str(el["authenticationMethods"]),
        is_Active = el["status"]["code"],
        Pictures = el["images"]
        )
        for el in resp
    ]
    return results
philINT/modules/software/adobe.py
ajuelosemmanuel/philINT
import re, pkgutil, importlib, asyncio, json, datetime, gpxpy, httpx, folium, pandas

# Some patterns that are useful
REGEX_EMAIL_CHECK = re.compile(r"([-!#-'*+/-9=?A-Z^-~]+(\.[-!#-'*+/-9=?A-Z^-~]+)*|\"([]!#-[^-~ \t]|(\\[\t -~]))+\")@([-!#-'*+/-9=?A-Z^-~]+(\.[-!#-'*+/-9=?A-Z^-~]+)*|\[[\t -Z^-~]*])")
REGEX_IMAGE_LINK = re.compile(r"(http)?s?:?(\/\/[^\"']*\.(?:png|jpg|jpeg|gif|png|svg))")
REGEX_LINK = re.compile(r"(http|ftp|https):\/\/([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:\/~+#-]*[\w@?^=%&\/~+#-])")

def data_cleaner(connections:list[list[object]]) -> list[object]:
    """
    Turns a list of list of connections to a list of connections
    """
    res = []
    for el in connections:
        if type(el) == list and len(el) == 1:
            res.append(el[0])
        elif type(el) == list and len(el) > 1:
            for elem in el:
                res.append(elem)
        else:
            res.append(el)
    return res

def is_email(input:str) -> bool:
    """
    Checks if a string is an email address
    """
    return bool(re.fullmatch(REGEX_EMAIL_CHECK, input))

def dot_at_converter(text:str) -> str:
    """
    Replaces some of the other ways to design a dot, in order to find email addresses in a string.
    """
    return text.replace(' [dot] ', ".").replace('[dot]', ".").replace(' (dot) ', ".").replace('(dot)', ".").replace('dot', ".").replace(' [at] ', ".").replace('[at]', ".").replace(' (at) ', ".").replace('(at)', ".").replace('at', ".")

def extract_links_and_emails(text:str) -> tuple[list]:
    """
    Get all links, images and email addresses from a string.
    Output is a tuple of 3 lists.
    """
    text_processed = dot_at_converter(text)
    links_images = re.findall(REGEX_IMAGE_LINK, text_processed)
    clean_links_images = list(set([(el[0] + "s:" + el[1]).lower() for el in links_images]))
    text_processed = re.sub(REGEX_IMAGE_LINK, "", text_processed)
    links = re.findall(REGEX_LINK, text_processed)
    clean_links = list(set([(el[0] + "://" + el[1] + el[2]).lower() for el in links]))
    text_processed = re.sub(REGEX_LINK, "", text_processed)
    emails = re.findall(r"[\w\.-]+@[\w\.-]+\.\w+", text_processed)
    return clean_links, clean_links_images, emails

def remove_duplicate_unhashable(list_of_unhashable:list) -> list:
    """
    Used to keep unique Account-linked data tuples in a person object. Code isn't clean, but it gets the job done.
    """
    unique_lists = []
    unique_types = []
    for sublist in list_of_unhashable:
        type_sub = type(sublist).__name__
        if type_sub not in unique_types or sublist not in unique_lists :
            unique_lists.append(sublist)
            unique_types.append(type_sub)
    return unique_lists

def import_submodules(package = "philINT.modules"):
    """
    Get all the submodules
    Adapted from holehe's code
    """
    if isinstance(package, str):
        package = importlib.import_module(package)
    results = {}
    for _, name, is_pkg in pkgutil.walk_packages(package.__path__):
        full_name = package.__name__ + '.' + name
        results[full_name] = importlib.import_module(full_name)
        if is_pkg:
            results.update(import_submodules(full_name))
    return results

def get_functions(modules):
    """
    Transform the modules objects to functions
    Adapted from holehe's code
    """
    from_email = []
    from_username = []
    for module in modules:
        if len(module.split(".")) > 3:
            if "from_email" in modules[module].__dict__:
                from_email.append(modules[module].__dict__["from_email"])
            if "from_username" in modules[module].__dict__:
                from_username.append(modules[module].__dict__["from_username"])
    return from_email, from_username

from_email, from_username = get_functions(import_submodules())

async def get_data_from_email_coro(email:str) -> list:
    """Get all the data you can gather from an email address"""
    results = []
    for m in from_email:
        result = await m(email)
        if result != None:
            results.append(result)
    return data_cleaner(results)

async def get_data_from_username_coro(username:str) -> list:
    """Get all the data you can gather from an username"""
    results = []
    for m in from_username:
        result = await m(username)
        if result != None:
            results.append(result)
    return data_cleaner(results)

def get_data_from_email(email:str) -> list:
    """
    I hate forgetting await - why not remove it ?
    Be careful - it is NOT working in jupyter notebooks !! Please use get_data_from_email_coro
    """
    loop = asyncio.get_event_loop()
    task = loop.create_task(get_data_from_email_coro(email))
    loop.run_until_complete(task)
    return task.result()

def get_data_from_username(username:str) -> list:
    """
    I hate forgetting await - why not remove it ?
    Be careful - it is NOT working in jupyter notebooks !! Please use get_data_from_username_coro
    """
    loop = asyncio.get_event_loop()
    task = loop.create_task(get_data_from_username_coro(username))
    loop.run_until_complete(task)
    return task.result()

def all_data_to_dict(all_data):
    """
    Makes a dictionnary from the all_data field
    """
    return {type(el).__name__:{el._fields[i]:el[i] for i in range(len(el)) if el[i] is not None} for el in all_data}

def all_data_export(all_data, file_name:str="data_export"):
    """
    Saves the all_data field to a .json file.
    Default file name is "data_export_[timestamp]"
    """
    dico = all_data_to_dict(all_data)
    json_string = json.dumps(dico, sort_keys=True, indent=4)
    timestamp = ""
    if file_name == "data_export":
        timestamp = "_" + str(round(datetime.datetime.timestamp(datetime.datetime.now())))
    with open(file_name + timestamp + ".json", "w") as f:
        f.write(json_string)

def isnamedtupleinstance(x):
    t = type(x)
    b = t.__bases__
    if len(b) != 1 or b[0] != tuple: return False
    f = getattr(t, '_fields', None)
    if not isinstance(f, tuple): return False
    return all(type(n)==str for n in f)

def display_namedtuple(nt, with_title = True, tab = ""):
    """
    Displays a NamedTuple in a nice way
    """
    if with_title:
        print(tab + type(nt).__name__.replace("_", " ") + " :")
    for i in range(len(nt)):
        to_print = "\t • "
        to_print += nt._fields[i].replace("_", " ") + " : "
        if type(nt[i]) == str:
            to_print += nt[i].replace("_", " ")
            print(tab + to_print)
        elif type(nt[i]) == list:
            print(tab + to_print)
            for el in nt[i]:
                if isnamedtupleinstance(el):
                    display_namedtuple(el, with_title = False, tab= tab + "\t")
                    if el != nt[i][-1]:
                        print()
                else:
                    print("\t • " + str(el))
        elif isnamedtupleinstance(nt[i]):
            print(tab + to_print)
            display_namedtuple(nt[i], with_title = False, tab="\t")
        else:
            to_print += str(nt[i])
            print(tab + to_print)

def all_data_display(all_data):
    """
    Displays the all_data field in a nicer way
    """
    for nt in all_data:
        display_namedtuple(nt)
        print()


def print_results_banner():
    print("philINT - by Emmanuel Ajuelos (https://github.com/ajuelosemmanuel)")
    print("-------------------------------------")
    print("|              Results              |")
    print("-------------------------------------")

async def get_additionnal_info_email_eva_coro(email:str) -> tuple:
    """
    Coroutine to get information from Email Verification API (EVA - eva.pingutil.com)
    """
    async with httpx.AsyncClient() as client:
        eva_data = await client.get("http://api.eva.pingutil.com/email?email=" + email)
    if eva_data.json()["status"] == "failure":
        print("EVA couldn't get information about the email address.")
        return
    return eva_data.json()["data"]["spam"], eva_data.json()["data"]["deliverable"], eva_data.json()["data"]["disposable"]

def get_additionnal_info_email_eva(email:str) -> list:
    """
    I hate forgetting await - why not remove it ?
    Be careful - it is NOT working in jupyter notebooks !! Please use get_additionnal_info_email_eva_coro
    """
    loop = asyncio.get_event_loop()
    task = loop.create_task(get_additionnal_info_email_eva_coro(email))
    loop.run_until_complete(task)
    return task.result()

def load_gpx_file(file_path:str) -> pandas.DataFrame:
    """
    Load a GPX file into a Pandas DataFrame
    """
    with open(file_path, 'r') as gpx_file:
        gpx = gpxpy.parse(gpx_file)
        route_info = []
    for track in gpx.tracks:
        for segment in track.segments:
            for point in segment.points:
                route_info.append({
                    'latitude': point.latitude,
                    'longitude': point.longitude,
                    'elevation': point.elevation
                })
    return pandas.DataFrame(route_info)

def display_gpx_dataframe(gpx_dataframe:pandas.DataFrame, save:bool=False):
    """
    Displays a DataFrame using OpenStreetMap
    """
    route_map = folium.Map(
    location=[gpx_dataframe["latitude"][0], gpx_dataframe["longitude"][0]],
    zoom_start=13,
    tiles='OpenStreetMap',
    )
    coordinates = [tuple(x) for x in gpx_dataframe[['latitude', 'longitude']].to_numpy()]
    folium.PolyLine(coordinates, weight=6).add_to(route_map)
    route_map.show_in_browser()
    if save:
        route_map.save(outfile = datetime.datetime.now().strftime('%Y%m%d%H%M') + ".html")
philINT/utils.py
ajuelosemmanuel/philINT
from setuptools import setup, find_packages

setup(
    name="philINT",
    version="1.00",
    packages=find_packages(),
    author="Emmanuel Ajuelos",
    author_email="ajuelosemmanuel@gmail.com",
    install_requires=["iso-639","pycountry","httpx","selenium", "gpxpy", "folium", "asyncio", "pandas"],
    description="philINT is a library that allows OSINT investigators to retrieve and organize data from an email address or an username.",
    include_package_data=True,
    url="https://github.com/ajuelosemmanuel/philINT",
    entry_points = {
        "console_scripts": [
            "philINT = philINT.main:main",
        ]
    },
    classifiers=[
        "Programming Language :: Python",
        "License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
    ],
)
setup.py
ajuelosemmanuel/philINT