Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,9 @@ and [issues](https://github.com/vil/H4X-Tools/issues) page to see if there is an

## Submitting changes

After you have forked the project and done your changes. Open a pull request and tell what you have changed, improved
After you have forked the project and done your changes. Open up a pull request and tell what you have changed, improved
or added.

Also avoid using AI, human written code is always better.

Don't also write **random** commit messages, simply tell shortly what you have done.


-------------------------
Thank you for understanding.
Thank you.
29 changes: 14 additions & 15 deletions h4xtools.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/env python3

"""
Copyright (c) 2023-2025. Vili and contributors.
Copyright (c) 2023-2026. Vili and contributors.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
Expand All @@ -24,7 +24,7 @@

from helper import handles, printer

VERSION = "0.3.3"
VERSION = "0.3.4"


def internet_check() -> None:
Expand Down Expand Up @@ -160,9 +160,7 @@ def main() -> None:
user_input = printer.user_input("Tool to execute : \t")

if user_input.lower() in {"quit", "exit", "q", "kill"}:
"""
Kills the program.
"""
# Kill the program.
printer.warning("Quitting... Goodbye!")
print(Style.RESET_ALL)
time.sleep(0.5)
Expand All @@ -184,13 +182,14 @@ def main() -> None:


if __name__ == "__main__":
try:
main()
except ValueError:
printer.error("Invalid value inputted..!")
main() # re-run
except KeyboardInterrupt:
print("\n")
printer.warning("Quitting... Goodbye!")
print(Style.RESET_ALL)
exit(0)
while True:
try:
main()
break
except ValueError:
printer.error("Invalid value inputted..!")
except KeyboardInterrupt:
print("\n")
printer.warning("Quitting... Goodbye!")
print(Style.RESET_ALL)
exit(0)
4 changes: 1 addition & 3 deletions helper/printer.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""
Copyright (c) 2023-2025. Vili and contributors.
Copyright (c) 2023-2026. Vili and contributors.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
Expand Down Expand Up @@ -55,8 +55,6 @@ def warning(message, *args, **kwargs) -> None:
def debug(message, *args, **kwargs) -> None:
if "--debug" in sys.argv:
print_colored(message, Fore.LIGHTMAGENTA_EX, "[>]", *args, **kwargs)
else:
pass


def noprefix(message, *args, **kwargs) -> None:
Expand Down
2 changes: 1 addition & 1 deletion helper/randomuser.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""
Copyright (c) 2023-2025. Vili and contributors.
Copyright (c) 2023-2026. Vili and contributors.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
Expand Down
4 changes: 3 additions & 1 deletion helper/timer.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""
Copyright (c) 2023-2025. Vili and contributors.
Copyright (c) 2023-2026. Vili and contributors.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
Expand All @@ -15,6 +15,7 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""

import functools
import time
from types import FunctionType

Expand All @@ -30,6 +31,7 @@ def timer(require_input: bool) -> FunctionType:
"""

def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs) -> str:
start_time = time.time() # Start timing
result = func(*args, **kwargs) # Execute the wrapped function
Expand Down
9 changes: 5 additions & 4 deletions helper/url_helper.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""
Copyright (c) 2023-2025. Vili and contributors.
Copyright (c) 2023-2026. Vili and contributors.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
Expand All @@ -22,12 +22,13 @@
from helper import printer


def read_local_content(path) -> str:
def read_local_content(path) -> str | dict | None:
"""
Reads file content from a local file.

:param path: path to the file
:return: Content of the file as a string or None if an error occurs.
:return: Parsed dict for JSON files, raw string for text files,
or None if an error occurs.
"""
try:
with open(resource_path(path), "r") as file:
Expand All @@ -38,7 +39,7 @@ def read_local_content(path) -> str:
return content
except Exception as e:
printer.error(f"An error occurred: {str(e)}")
return "None"
return None


# I hate pyinstaller.
Expand Down
4 changes: 2 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ beautifulsoup4
urllib3
pyinstaller
whoisdomain
requests
git+https://github.com/diezo/Ensta.git
snscrape
faker
holehe
ignorant
httpx
aiohttp
asyncio
psutil
19 changes: 13 additions & 6 deletions utils/bluetooth_scanner.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""
Copyright (c) 2023-2025. Vili and contributors.
Copyright (c) 2023-2026. Vili and contributors.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
Expand Down Expand Up @@ -65,6 +65,10 @@ def scan_linux(duration: int) -> str:
bufsize=1,
)

if process.stdin is None:
printer.error("Failed to open bluetoothctl stdin.")
return output

process.stdin.write("scan on\n")
process.stdin.flush()

Expand Down Expand Up @@ -105,20 +109,23 @@ def parse_output(output: str, platform: str) -> None:
for line in clean_output.splitlines():
printer.debug(line)
if "[NEW] Device" in line:
parts = line.split(" ", 4)
parts = line.split()
printer.debug(parts)
# parts[0] = [NEW]
# parts[1] = Device
# parts[2] = MAC
# parts[3+] = device name
# parts[2] = MAC address
# parts[3:] = device name (may contain spaces)
if len(parts) < 3:
continue
mac = parts[2]
name = parts[4].strip() if len(parts) > 4 else "No Name"
name = " ".join(parts[3:]).strip() if len(parts) > 3 else "No Name"

devices.append({"mac": mac, "name": name, "raw": line})

printer.info("Nearby devices :")
for device in devices:
printer.success(f"Device: {device['name']} ({device['mac']})")
printer.success(f"RAW: {device['raw']}", "\n")
printer.success(f"RAW: {device['raw']}")
print()
case _:
printer.error("idk how u got here.")
88 changes: 49 additions & 39 deletions utils/dirbuster.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""
Copyright (c) 2023-2025. Vili and contributors.
Copyright (c) 2023-2026. Vili and contributors.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
Expand All @@ -18,89 +18,99 @@
import asyncio

import aiohttp
import requests
from colorama import Style

from helper import printer, randomuser, timer, url_helper

url_set = set()
target_domain: str | None = None


@timer.timer(require_input=True)
def bust(domain: str) -> None:
"""
Scans the given url for valid paths
Scans the given url for valid paths.

param domain: url to scan
:param domain: url to scan
"""
target_domain = domain

printer.info(
f"Scanning for valid URLs for {Style.BRIGHT}{target_domain}{Style.RESET_ALL}..."
f"Scanning for valid URLs for {Style.BRIGHT}{domain}{Style.RESET_ALL}..."
)
printer.warning("This may take a while...")

scan_urls()
valid_urls = scan_urls(domain)

printer.success(
f"Scan Completed..! There were {Style.BRIGHT}{len(url_set)}{Style.RESET_ALL} valid URLs!"
f"Scan Completed..! There were {Style.BRIGHT}{len(valid_urls)}{Style.RESET_ALL} valid URLs!"
)


def get_wordlist() -> set[str] | None:
"""
Reads the wordlist from the url and returns a list of names
Reads the wordlist from the local resources and returns a set of paths.

:return: list of names
:return: set of path strings, or None if an error occurs.
"""
try:
content = url_helper.read_local_content("resources/wordlist.txt")
return {line.strip() for line in content.splitlines() if line.strip()}
except requests.exceptions.ConnectionError:
content = url_helper.read_local_content("resources/wordlist.txt")
if not isinstance(content, str):
return None
return {line.strip() for line in content.splitlines() if line.strip()}


async def fetch_url(session: aiohttp.ClientSession, path: str) -> None:
async def fetch_url(
session: aiohttp.ClientSession, domain: str, path: str, url_set: set
) -> None:
"""
Fetches the url and checks if it is valid
Fetches a URL and records it if it returns HTTP 200.

:param session: aiohttp session
:param domain: target domain
:param path: path to check
:param url_set: set to collect valid URLs into
"""
url = f"https://{target_domain}/{path}"
headers = {"User-Agent": f"{randomuser.GetUser()}"}
async with session.get(url, headers=headers) as response:
if response.status == 200:
printer.success(
f"{len(url_set) + 1} Valid URL(s) found : {Style.BRIGHT}{url}{Style.RESET_ALL}"
)
url_set.add(url)
url = f"https://{domain}/{path}"
headers = {"User-Agent": str(randomuser.GetUser())}
try:
async with session.get(url, headers=headers) as response:
if response.status == 200:
url_set.add(url)
printer.success(
f"{len(url_set)} Valid URL(s) found : {Style.BRIGHT}{url}{Style.RESET_ALL}"
)
except Exception:
pass


async def scan_async(paths: set[str]) -> None:
async def scan_async(domain: str, paths: set[str]) -> set[str]:
"""
Scans the url asynchronously
Scans the domain asynchronously for all paths.

:param domain: target domain
:param paths: set of paths to scan
:return: set of valid URLs found
"""
url_set: set[str] = set()
async with aiohttp.ClientSession() as session:
tasks = [fetch_url(session, path) for path in paths]
tasks = [fetch_url(session, domain, path, url_set) for path in paths]
await asyncio.gather(*tasks, return_exceptions=True)
return url_set


def scan_urls() -> None:
def scan_urls(domain: str) -> set[str]:
"""
Loads the wordlist and runs the async scan.

:param domain: target domain
:return: set of valid URLs found
"""
paths = get_wordlist()
printer.debug(target_domain, paths)
printer.debug(f"Domain: {domain}, paths loaded: {len(paths) if paths else 0}")
if paths is None:
printer.error("Connection Error..!")
return
printer.error("Failed to load wordlist..!")
return set()

try:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop.run_until_complete(scan_async(paths))
return asyncio.run(scan_async(domain, paths))
except KeyboardInterrupt:
printer.error("Cancelled..!")
return set()
except RuntimeError as e:
printer.error("Ran into a RuntimeError:", e)
printer.error(f"Ran into a RuntimeError: {e}")
return set()
2 changes: 1 addition & 1 deletion utils/email_search.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""
Copyright (c) 2023-2025. Vili and contributors.
Copyright (c) 2023-2026. Vili and contributors.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
Expand Down
2 changes: 1 addition & 1 deletion utils/fake_info_generator.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""
Copyright (c) 2023-2025. Vili and contributors.
Copyright (c) 2023-2026. Vili and contributors.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
Expand Down
6 changes: 5 additions & 1 deletion utils/ig_scrape.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""
Copyright (c) 2023-2025. Vili and contributors.
Copyright (c) 2023-2026. Vili and contributors.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
Expand Down Expand Up @@ -43,6 +43,10 @@ def scrape(target: str) -> None:
printer.error("You are being rate limited..!")
except APIError as e:
printer.error("There is a issue with the API:", e)
except AttributeError:
printer.error(
"Couldn't get any data, you may need to change IP's or disable your VPN for a while..!"
)


def print_scraped_data(data: dict) -> None:
Expand Down
Loading