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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ A modular, terminal-based toolkit for OSINT, reconnaissance, and scraping - buil
| 14 | **Dir Buster** | Asynchronously bruteforces directory and file paths on a target website using a built-in wordlist, printing every URL that returns HTTP 200. |
| 15 | **Bluetooth Scanner** | Scans for nearby Bluetooth devices via `bluetoothctl` (Linux) and reports device names and MAC addresses. *(Windows support coming soon.)* |
| 16 | **Local Users** | Enumerates all local user accounts on the system. On Linux: username, UID, GID, full name, home directory, shell, and group. On Windows: username, terminal, host, session start time, PID, SID, and domain. |
| 17 | **Git Scrape** | Scrapes and analyzes a Git repository (remote/local) or a GitHub account for possible personal email leaks and other data. *(Other platforms will be supported soon.)* |

---

Expand Down
6 changes: 3 additions & 3 deletions h4xtools.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ def _get_git_ref() -> str:
return result.stdout.strip()
except (subprocess.CalledProcessError, FileNotFoundError):
# Fallback if git fails (e.g., not a git repo) or git is not installed
printer.warning(
"Failed to fetch the version hash! Make sure you are running this in the parent folder!"
printer.verbose(
"Failed to fetch the build hash! Make sure you are running this in the parent folder!"
)
return "26"

Expand Down Expand Up @@ -79,7 +79,7 @@ def _print_banner() -> None:
██▀▐█ ·██· ▐█.▪ ▄█▀▄ ▄█▀▄ ██▪ ▄▀▀▀█▄
██▌▐▀▪▐█·█▌ ▐█▌·▐█▌.▐▌▐█▌.▐▌▐█▌▐▌▐█▄▪▐█
▀▀▀ ·•▀▀ ▀▀ ▀▀▀ ▀█▄▀▪ ▀█▄▀▪.▀▀▀ ▀▀▀▀
{Style.RESET_ALL}build: {VERSION} / Vili (@vil) / https://vili.dev
{Style.RESET_ALL}Build {VERSION} / Vili (@vil) / https://vili.dev
"""
)

Expand Down
147 changes: 147 additions & 0 deletions tools/git_scrape.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
"""
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
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""

from colorama import Style

from helper import config, printer
from tools.base import BaseTool, ToolArgument

_CONFIG_SECTION = "git_scrape"
_TOKEN_KEY = "github_token"


def _prompt_new_token() -> str:
"""
Prompt for a new GitHub Personal Access Token and optionally save it.

:return: Token string, or an empty string for guest mode.
"""
printer.noprefix("")
printer.info(
"Provide a GitHub Personal Access Token for higher rate limits and access to private repos."
)
printer.info("Generate one at: https://github.com/settings/tokens")
token = printer.user_input("GitHub Token (leave blank for guest mode) : ").strip()

if not token:
return ""

printer.warning(
"Tokens are sensitive secrets. Saving stores it as plaintext in an owner-only config file."
)
answer = (
printer.user_input(
"Save this token to H4X-Tools config for future runs? (y/N) : "
)
.strip()
.lower()
)
if answer in {"y", "yes"}:
if config.set_value(_CONFIG_SECTION, _TOKEN_KEY, token):
printer.success(
f"Token saved to {Style.BRIGHT}{config.get_config_file()}{Style.RESET_ALL}"
)

return token


def _ask_github_token() -> str:
"""
Resolve the GitHub Token from saved config or user input.

Saved values live in ``$HOME/.config/h4x-tools/config.json`` by default,
using the shared H4X-Tools config helper.

:return: Token string, or an empty string for guest mode.
"""
saved_token = config.get_value(_CONFIG_SECTION, _TOKEN_KEY, "")
if isinstance(saved_token, str) and saved_token.strip():
printer.info("Saved GitHub Token found in H4X-Tools config.")
printer.info(" 1 : Use saved token")
printer.info(" 2 : Enter a different token")
printer.info(" 3 : Delete saved token and use guest mode")
printer.info(" 4 : Use guest mode once")
choice = printer.user_input("Select [1-4] (default = 1) : ").strip()

match choice:
case "" | "1":
return saved_token.strip()
case "2":
return _prompt_new_token()
case "3":
if config.delete_value(_CONFIG_SECTION, _TOKEN_KEY):
printer.success("Saved GitHub Token deleted.")
return ""
case "4":
return ""
case _:
printer.warning("Invalid choice. Using saved token.")
return saved_token.strip()

return _prompt_new_token()


class GitScrape(BaseTool):
id = "git_scrape"
name = "Git Scrape"
order = 17
aliases = ("--git", "--git-scrape", "--gitscrape")
description = "Scrapes and analyzes a Git repository (remote/local) or a GitHub account for possible personal email leaks and other data."
arguments = (
ToolArgument(
"target",
"USERNAME_OR_PATH_OR_URL",
"Run Git scrape on a GitHub account / Local repo / Remote repo.",
),
)

def run(self, target: str | None = None) -> None:
"""
Execute the Git Scrape tool.

Resolves user input if parameters are omitted and passes execution
to the core scrape utility based on the target type.

:param target: GitHub username, local path, or remote URL to a Git repository.
"""
from utils import git_scrape

if not target:
target = printer.user_input(
"Enter a target GitHub username OR GitHub repo path/url : \t"
).strip()

if not target:
printer.error("No target provided. Exiting tool.")
return

# Dynamically route the single target parameter
if "/" in target or target.endswith(".git") or target.startswith("http"):
repo_target = target
user_target = None
else:
repo_target = None
user_target = target

token = _ask_github_token()

try:
git_scrape.execute_scrape(
target=user_target, repository=repo_target, token=token
)
except Exception as exc:
printer.error(f"An error occurred during scraping: {exc}")
1 change: 0 additions & 1 deletion tools/ig_scrape.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ class IgScrapeTool(BaseTool):
description = (
"Two-track Instagram OSINT scraper. Guest mode uses the ensta Guest API for public profile data and recent posts. "
"Authenticated mode can query Instagram's private mobile API for richer public account data. Results can be exported as TXT, CSV, or JSON. "
"New CLI flags: --session-id, --skip-obf (true/false), --comments-limit N."
)
arguments = (
ToolArgument("target", "USERNAME", "Run Instagram scrape for USERNAME."),
Expand Down
Loading