feat(infra): auto-commit con 88 cambios
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
"""Comprueba la existencia de un username en sitios publicos (sherlock ligero).
|
||||
|
||||
OSINT pasivo: para un username dado, consulta la URL de perfil de una lista
|
||||
de sitios conocidos y deduce si la cuenta existe por el codigo de estado
|
||||
HTTP (200 = existe, 404 = no existe). Cada sitio se consulta de forma
|
||||
aislada: un fallo (timeout, error de red) no aborta el resto.
|
||||
"""
|
||||
|
||||
import requests
|
||||
|
||||
# Cada entrada describe un sitio: como construir la URL de perfil a partir
|
||||
# del username. La deteccion es por codigo de estado (200 existe / 404 no).
|
||||
DEFAULT_SITES = [
|
||||
{"site": "github", "url": "https://github.com/{u}"},
|
||||
{"site": "twitter", "url": "https://x.com/{u}"},
|
||||
{"site": "instagram", "url": "https://www.instagram.com/{u}/"},
|
||||
{"site": "tiktok", "url": "https://www.tiktok.com/@{u}"},
|
||||
{"site": "reddit", "url": "https://www.reddit.com/user/{u}"},
|
||||
{"site": "gitlab", "url": "https://gitlab.com/{u}"},
|
||||
{"site": "keybase", "url": "https://keybase.io/{u}"},
|
||||
{"site": "medium", "url": "https://medium.com/@{u}"},
|
||||
{"site": "telegram", "url": "https://t.me/{u}"},
|
||||
{"site": "youtube", "url": "https://www.youtube.com/@{u}"},
|
||||
{"site": "pinterest", "url": "https://www.pinterest.com/{u}/"},
|
||||
{"site": "about_me", "url": "https://about.me/{u}"},
|
||||
]
|
||||
|
||||
_BROWSER_UA = (
|
||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 "
|
||||
"(KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36"
|
||||
)
|
||||
|
||||
|
||||
def enumerate_username_sites(
|
||||
username: str,
|
||||
timeout_s: float = 8.0,
|
||||
sites: list | None = None,
|
||||
) -> list:
|
||||
"""Comprueba si un username existe en una lista de sitios publicos.
|
||||
|
||||
Para cada sitio construye la URL de perfil con el username y hace un
|
||||
GET con User-Agent de navegador siguiendo redirecciones. Interpreta el
|
||||
codigo de estado final: 200 -> existe, 404 -> no existe, cualquier otro
|
||||
-> indeterminado (exists=None). Los errores de red por sitio (timeout,
|
||||
conexion) se capturan y se reportan con status=None y exists=None sin
|
||||
interrumpir la enumeracion del resto.
|
||||
|
||||
Args:
|
||||
username: nombre de usuario a buscar (sin arroba ni URL).
|
||||
timeout_s: timeout en segundos por peticion. Default 8.0.
|
||||
sites: lista opcional de dicts {"site", "url"} donde "url" contiene
|
||||
el placeholder "{u}". Si es None se usa DEFAULT_SITES.
|
||||
|
||||
Returns:
|
||||
lista de dicts {"site", "url", "exists", "status"} en el mismo orden
|
||||
que la lista de sitios. "exists" es True/False/None y "status" es el
|
||||
codigo HTTP (int) o None si la peticion fallo.
|
||||
"""
|
||||
targets = sites if sites is not None else DEFAULT_SITES
|
||||
headers = {"User-Agent": _BROWSER_UA}
|
||||
results = []
|
||||
|
||||
for entry in targets:
|
||||
site = entry.get("site", "")
|
||||
url = entry["url"].format(u=username)
|
||||
status = None
|
||||
exists = None
|
||||
try:
|
||||
resp = requests.get(
|
||||
url,
|
||||
headers=headers,
|
||||
timeout=timeout_s,
|
||||
allow_redirects=True,
|
||||
)
|
||||
status = resp.status_code
|
||||
if status == 200:
|
||||
exists = True
|
||||
elif status == 404:
|
||||
exists = False
|
||||
else:
|
||||
exists = None
|
||||
except requests.RequestException:
|
||||
# Timeout, error de conexion, demasiados redirects, etc.
|
||||
status = None
|
||||
exists = None
|
||||
|
||||
results.append(
|
||||
{"site": site, "url": url, "exists": exists, "status": status}
|
||||
)
|
||||
|
||||
return results
|
||||
Reference in New Issue
Block a user