moriarty-project 0.1.26__py3-none-any.whl → 0.1.27__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- moriarty/__init__.py +1 -1
- moriarty/cli/app.py +2 -2
- moriarty/cli/domain_cmd.py +127 -634
- moriarty/modules/domain_scanner.py +21 -16
- moriarty/modules/port_scanner_nmap.py +369 -102
- {moriarty_project-0.1.26.dist-info → moriarty_project-0.1.27.dist-info}/METADATA +5 -5
- {moriarty_project-0.1.26.dist-info → moriarty_project-0.1.27.dist-info}/RECORD +9 -11
- moriarty/cli/wifippler.py +0 -124
- moriarty/modules/port_scanner.py +0 -1050
- {moriarty_project-0.1.26.dist-info → moriarty_project-0.1.27.dist-info}/WHEEL +0 -0
- {moriarty_project-0.1.26.dist-info → moriarty_project-0.1.27.dist-info}/entry_points.txt +0 -0
moriarty/cli/domain_cmd.py
CHANGED
@@ -1,19 +1,16 @@
|
|
1
1
|
"""Comandos de scanning de domínios/IPs."""
|
2
2
|
|
3
|
-
import
|
4
|
-
import json
|
5
|
-
from typing import Optional, Dict, List
|
3
|
+
from typing import Optional
|
6
4
|
|
7
5
|
import typer
|
8
|
-
from rich.console import Console
|
9
|
-
from rich.panel import Panel
|
10
|
-
from rich.table import Table, box
|
11
6
|
|
12
7
|
from moriarty.modules.web_crawler import WebCrawler
|
13
|
-
|
8
|
+
|
9
|
+
from moriarty.modules.port_scanner_nmap import PortScanner, PROFILES
|
14
10
|
from moriarty.modules.passive_recon import PassiveRecon
|
11
|
+
from rich.console import Console
|
15
12
|
|
16
|
-
app = typer.Typer(help="
|
13
|
+
app = typer.Typer(name="domain", help="🌐 Domain/IP reconnaissance and scanning.")
|
17
14
|
console = Console()
|
18
15
|
|
19
16
|
|
@@ -29,6 +26,7 @@ def scan_full(
|
|
29
26
|
stealth: int = typer.Option(0, "--stealth", "-s", help="Stealth level (0-4)"),
|
30
27
|
threads: int = typer.Option(10, "--threads", "-t", help="Threads concorrentes"),
|
31
28
|
timeout: int = typer.Option(30, "--timeout", help="Timeout em segundos"),
|
29
|
+
ports: str = typer.Option("quick", "--ports", "-p", help="Perfil de portas: quick, web, db, full, all"),
|
32
30
|
output: str = typer.Option(None, "--output", "-o", help="Arquivo de saída"),
|
33
31
|
verbose: bool = typer.Option(False, "--verbose", help="Ativar saída detalhada"),
|
34
32
|
):
|
@@ -49,9 +47,13 @@ def scan_full(
|
|
49
47
|
logging.getLogger("httpcore").setLevel(logging.ERROR)
|
50
48
|
logging.getLogger("moriarty").setLevel(logging.ERROR)
|
51
49
|
|
50
|
+
# Converte a string de módulos para lista
|
51
|
+
modules_list = modules.split(",") if modules != "all" else None
|
52
|
+
|
52
53
|
scanner = DomainScanner(
|
53
54
|
target=target,
|
54
|
-
modules=
|
55
|
+
modules=modules_list,
|
56
|
+
ports_profile=ports,
|
55
57
|
stealth_level=stealth,
|
56
58
|
threads=threads,
|
57
59
|
timeout=timeout,
|
@@ -62,7 +64,7 @@ def scan_full(
|
|
62
64
|
|
63
65
|
if output:
|
64
66
|
scanner.export(output)
|
65
|
-
console.print(f"[green]✅[/green] Results saved to: [
|
67
|
+
console.print(f"[green]✅[/green] Results saved to: [cyan]{output}[/cyan]\n")
|
66
68
|
|
67
69
|
|
68
70
|
@app.command("subdiscover")
|
@@ -102,7 +104,7 @@ def subdomain_discovery(
|
|
102
104
|
from moriarty.modules.subdomain_discovery import SubdomainDiscovery
|
103
105
|
import asyncio
|
104
106
|
|
105
|
-
console.print(f"[bold
|
107
|
+
console.print(f"[bold cyan]🔍 Descobrindo subdomínios de:[/bold cyan] {domain}\n")
|
106
108
|
|
107
109
|
discovery = SubdomainDiscovery(
|
108
110
|
domain=domain,
|
@@ -142,7 +144,7 @@ def wayback_urls(
|
|
142
144
|
from moriarty.modules.wayback_discovery import WaybackDiscovery
|
143
145
|
import asyncio
|
144
146
|
|
145
|
-
console.print(f"[bold
|
147
|
+
console.print(f"[bold cyan]🕰️ Buscando URLs históricas de:[/bold cyan] {domain}\n")
|
146
148
|
|
147
149
|
wayback = WaybackDiscovery(
|
148
150
|
domain=domain,
|
@@ -182,7 +184,7 @@ def template_scan(
|
|
182
184
|
|
183
185
|
from moriarty.modules.template_scanner import TemplateScanner
|
184
186
|
|
185
|
-
console.print(f"[bold
|
187
|
+
console.print(f"[bold cyan]📝 Template scan em:[/bold cyan] {target}\n")
|
186
188
|
|
187
189
|
scanner = TemplateScanner(
|
188
190
|
target=target,
|
@@ -228,7 +230,7 @@ def run_pipeline(
|
|
228
230
|
from moriarty.modules.pipeline_orchestrator import PipelineOrchestrator
|
229
231
|
import asyncio
|
230
232
|
|
231
|
-
console.print(f"[bold
|
233
|
+
console.print(f"[bold cyan]🔄 Executando pipeline:[/bold cyan] {pipeline_file}\n")
|
232
234
|
|
233
235
|
orchestrator = PipelineOrchestrator(
|
234
236
|
pipeline_file=pipeline_file,
|
@@ -284,7 +286,41 @@ def stealth_command(
|
|
284
286
|
console.print(f"[red]❌ Ação inválida: {action}[/red]")
|
285
287
|
|
286
288
|
|
287
|
-
|
289
|
+
@app.command("ports")
|
290
|
+
def port_scan(
|
291
|
+
target: str = typer.Argument(..., help="IP ou domínio"),
|
292
|
+
ports: str = typer.Option("common", "--ports", "-p", help="Portas: common, all, 1-1000, 80,443"),
|
293
|
+
scan_type: str = typer.Option("syn", "--type", "-t", help="Tipo: syn, tcp, udp"),
|
294
|
+
stealth: int = typer.Option(0, "--stealth", "-s", help="Stealth level (0-4)"),
|
295
|
+
output: str = typer.Option(None, "--output", "-o", help="Arquivo de saída"),
|
296
|
+
verbose: bool = typer.Option(False, "--verbose", help="Ativar saída detalhada"),
|
297
|
+
):
|
298
|
+
"""
|
299
|
+
🔌 Scan avançado de portas.
|
300
|
+
|
301
|
+
Exemplos:
|
302
|
+
moriarty domain ports 8.8.8.8
|
303
|
+
moriarty domain ports target.com --ports 1-10000
|
304
|
+
moriarty domain ports target.com --type syn --stealth 3
|
305
|
+
"""
|
306
|
+
from moriarty.modules.port_scanner import PortScanner
|
307
|
+
import asyncio
|
308
|
+
|
309
|
+
console.print(f"[bold cyan]🔌 Port scan em:[/bold cyan] {target}\n")
|
310
|
+
|
311
|
+
scanner = PortScanner(
|
312
|
+
target=target,
|
313
|
+
ports=ports,
|
314
|
+
scan_type=scan_type,
|
315
|
+
stealth_level=stealth,
|
316
|
+
)
|
317
|
+
|
318
|
+
results = asyncio.run(scanner.scan())
|
319
|
+
|
320
|
+
console.print(f"\n[green]✅ {len(results)} portas abertas encontradas[/green]")
|
321
|
+
|
322
|
+
if output:
|
323
|
+
scanner.export(results, output)
|
288
324
|
|
289
325
|
|
290
326
|
@app.command("waf-detect")
|
@@ -309,7 +345,7 @@ def waf_detection(
|
|
309
345
|
from moriarty.modules.waf_detector import WAFDetector
|
310
346
|
import asyncio
|
311
347
|
|
312
|
-
console.print(f"[bold
|
348
|
+
console.print(f"[bold cyan]🛡️ Detectando WAF em:[/bold cyan] {target}\n")
|
313
349
|
|
314
350
|
detector = WAFDetector(target=target)
|
315
351
|
waf_info = asyncio.run(detector.detect())
|
@@ -319,7 +355,7 @@ def waf_detection(
|
|
319
355
|
console.print(f"[dim]Confidence: {waf_info['confidence']}%[/dim]")
|
320
356
|
|
321
357
|
if bypass:
|
322
|
-
console.print("\n[
|
358
|
+
console.print("\n[cyan]🔓 Tentando bypass...[/cyan]")
|
323
359
|
bypass_methods = asyncio.run(detector.attempt_bypass())
|
324
360
|
|
325
361
|
for method in bypass_methods:
|
@@ -360,12 +396,12 @@ def passive_recon(
|
|
360
396
|
console = Console()
|
361
397
|
|
362
398
|
# Cabeçalho
|
363
|
-
console.print(f"\n[bold
|
399
|
+
console.print(f"\n[bold cyan]🌐 Moriarty Passive Recon[/bold cyan]")
|
364
400
|
console.print(f"[dim]Alvo:[/dim] {domain}")
|
365
401
|
console.print(f"[dim]Data:[/dim] {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
|
366
402
|
|
367
403
|
async def _run():
|
368
|
-
with console.status(f"[bold green]Coletando informações sobre [
|
404
|
+
with console.status(f"[bold green]Coletando informações sobre [cyan]{domain}[/cyan]..."):
|
369
405
|
recon = PassiveRecon(domain)
|
370
406
|
try:
|
371
407
|
return await recon.collect()
|
@@ -379,15 +415,15 @@ def passive_recon(
|
|
379
415
|
if status_only:
|
380
416
|
sub_count = sum(len(v) for v in payload["subdomains"].values())
|
381
417
|
console.print(f"[bold]🔍 Resumo do Reconhecimento[/bold]")
|
382
|
-
console.print(f" • [
|
383
|
-
console.print(f" • [
|
384
|
-
console.print(f" • [
|
418
|
+
console.print(f" • [cyan]Subdomínios:[/cyan] {sub_count} encontrados")
|
419
|
+
console.print(f" • [cyan]Fontes:[/cyan] {', '.join(payload['subdomains'].keys()) or 'nenhuma'}")
|
420
|
+
console.print(f" • [cyan]Credenciais vazadas:[/cyan] {len(payload['leaks'])}")
|
385
421
|
return
|
386
422
|
|
387
423
|
# Seção de Subdomínios
|
388
424
|
if payload.get("subdomains"):
|
389
425
|
table = Table(title="[bold]🌐 Subdomínios Encontrados[/bold]", box=box.ROUNDED)
|
390
|
-
table.add_column("Subdomínio", style="
|
426
|
+
table.add_column("Subdomínio", style="cyan")
|
391
427
|
table.add_column("Fonte", style="green")
|
392
428
|
|
393
429
|
for source, subdomains in payload["subdomains"].items():
|
@@ -399,7 +435,7 @@ def passive_recon(
|
|
399
435
|
# Seção de Tecnologias
|
400
436
|
if payload.get("technologies", {}).get("detections"):
|
401
437
|
tech_table = Table(title="[bold]🛠️ Tecnologias Detectadas[/bold]", box=box.ROUNDED)
|
402
|
-
tech_table.add_column("Tecnologia", style="
|
438
|
+
tech_table.add_column("Tecnologia", style="cyan")
|
403
439
|
tech_table.add_column("Confiança", style="green")
|
404
440
|
tech_table.add_column("Categoria", style="magenta")
|
405
441
|
|
@@ -414,7 +450,7 @@ def passive_recon(
|
|
414
450
|
|
415
451
|
# Seção de Segurança
|
416
452
|
security_table = Table(title="[bold]🔒 Análise de Segurança[/bold]", box=box.ROUNDED)
|
417
|
-
security_table.add_column("Item", style="
|
453
|
+
security_table.add_column("Item", style="cyan")
|
418
454
|
security_table.add_column("Status", style="green")
|
419
455
|
|
420
456
|
# Verifica HSTS
|
@@ -435,7 +471,7 @@ def passive_recon(
|
|
435
471
|
# Seção de Reputação
|
436
472
|
if payload.get("reputation"):
|
437
473
|
rep_table = Table(title="[bold]📊 Reputação do Domínio[/bold]", box=box.ROUNDED)
|
438
|
-
rep_table.add_column("Fonte", style="
|
474
|
+
rep_table.add_column("Fonte", style="cyan")
|
439
475
|
rep_table.add_column("Status", style="green")
|
440
476
|
|
441
477
|
for source, data in payload["reputation"].items():
|
@@ -458,7 +494,7 @@ def passive_recon(
|
|
458
494
|
if output:
|
459
495
|
with open(output, "w", encoding="utf-8") as handle:
|
460
496
|
json.dump(payload, handle, indent=2, ensure_ascii=False)
|
461
|
-
console.print(f"\n[green]✓[/green] Resultados salvos em: [
|
497
|
+
console.print(f"\n[green]✓[/green] Resultados salvos em: [cyan]{output}[/cyan]")
|
462
498
|
|
463
499
|
except Exception as e:
|
464
500
|
console.print(f"[red]✗ Erro durante o reconhecimento:[/red] {str(e)}")
|
@@ -468,384 +504,6 @@ def passive_recon(
|
|
468
504
|
console.print(f"[yellow]⚠ Log de erro salvo em: {output}[/yellow]")
|
469
505
|
|
470
506
|
|
471
|
-
import asyncio
|
472
|
-
|
473
|
-
async def _port_scan_async(
|
474
|
-
target: str,
|
475
|
-
profile: str,
|
476
|
-
stealth: int,
|
477
|
-
resolve_services: bool,
|
478
|
-
check_vulns: bool,
|
479
|
-
format: str,
|
480
|
-
output: Optional[str]
|
481
|
-
):
|
482
|
-
"""Função assíncrona que realiza a varredura de portas."""
|
483
|
-
# Código da função port_scan aqui...
|
484
|
-
console = Console()
|
485
|
-
|
486
|
-
# Valida os parâmetros
|
487
|
-
def validate_parameters():
|
488
|
-
if stealth < 0 or stealth > 5:
|
489
|
-
console.print("[red]Erro:[/red] O nível de stealth deve estar entre 0 e 5")
|
490
|
-
raise typer.Exit(1)
|
491
|
-
|
492
|
-
if profile not in PROFILES:
|
493
|
-
console.print(f"[red]Erro:[/red] Perfil inválido. Use um dos seguintes: {', '.join(PROFILES.keys())}")
|
494
|
-
raise typer.Exit(1)
|
495
|
-
|
496
|
-
# Configura o console do Rich
|
497
|
-
console = Console()
|
498
|
-
|
499
|
-
# Valida os parâmetros
|
500
|
-
validate_parameters()
|
501
|
-
|
502
|
-
# Usa o perfil especificado ou o padrão 'quick'
|
503
|
-
ports = PROFILES.get(profile.lower(), PROFILES["quick"])
|
504
|
-
|
505
|
-
# Configura o arquivo de saída
|
506
|
-
output_path = Path(output) if output else None
|
507
|
-
if output_path:
|
508
|
-
if output_path.suffix not in (".json", ".txt", ".md"):
|
509
|
-
console.print("[yellow]Aviso:[/yellow] Extensão de arquivo não suportada. Usando .json")
|
510
|
-
output_path = output_path.with_suffix(".json")
|
511
|
-
|
512
|
-
# Configura a barra de progresso
|
513
|
-
progress_columns = [
|
514
|
-
SpinnerColumn(),
|
515
|
-
TextColumn("[progress.description]{task.description}"),
|
516
|
-
BarColumn(bar_width=None),
|
517
|
-
TaskProgressColumn(),
|
518
|
-
TimeElapsedColumn(),
|
519
|
-
]
|
520
|
-
|
521
|
-
# Cabeçalho da varredura
|
522
|
-
console.rule(f"🔍 [bold red]Varredura de Portas[/bold red]")
|
523
|
-
|
524
|
-
# Tabela de informações
|
525
|
-
info_table = Table.grid(padding=(0, 1))
|
526
|
-
info_table.add_row("🌐 Alvo:", f"[bold]{target}")
|
527
|
-
info_table.add_row("🔢 Portas:", f"[red]{ports}")
|
528
|
-
info_table.add_row("🛡️ Nível de stealth:", f"[yellow]{stealth}")
|
529
|
-
info_table.add_row("🔍 Detecção de serviços:", "[green]Ativada[/green]" if resolve_services else "[red]Desativada[/red]")
|
530
|
-
info_table.add_row("⚠️ Verificação de vulnerabilidades:", "[green]Ativada[/green]" if check_vulns else "[red]Desativada[/red]")
|
531
|
-
|
532
|
-
console.print(Panel(info_table, title="[bold]Configuração da Varredura[/bold]", border_style="red"))
|
533
|
-
console.print()
|
534
|
-
|
535
|
-
async def run_scan() -> List[PortScanResult]:
|
536
|
-
"""Executa a varredura de portas."""
|
537
|
-
# Usa sempre TCP Connect que não requer privilégios de root
|
538
|
-
console.print("[yellow]Aviso:[/yellow] Usando varredura TCP Connect (não requer privilégios de root).")
|
539
|
-
scan_type = "tcp"
|
540
|
-
|
541
|
-
# Cria o scanner Nmap
|
542
|
-
scanner = PortScanner(
|
543
|
-
target=target,
|
544
|
-
ports=ports,
|
545
|
-
scan_type=scan_type,
|
546
|
-
stealth_level=stealth,
|
547
|
-
resolve_services=resolve_services,
|
548
|
-
check_vulns=check_vulns,
|
549
|
-
)
|
550
|
-
|
551
|
-
# Executa o scan e retorna os resultados
|
552
|
-
return await scanner.scan()
|
553
|
-
|
554
|
-
console.print("🚀 Iniciando varredura de portas...\n")
|
555
|
-
|
556
|
-
# Executa a varredura com barra de progresso
|
557
|
-
with Progress(*progress_columns) as progress:
|
558
|
-
task = progress.add_task("[red]Escaneando portas...", total=100)
|
559
|
-
|
560
|
-
try:
|
561
|
-
# Executa o scan de forma assíncrona
|
562
|
-
results = await run_scan()
|
563
|
-
progress.update(task, completed=100)
|
564
|
-
|
565
|
-
if not results:
|
566
|
-
console.print("\n[yellow]⚠️ Nenhuma porta aberta detectada[/yellow]")
|
567
|
-
return
|
568
|
-
|
569
|
-
output_format = format.lower()
|
570
|
-
|
571
|
-
if output_format == "json":
|
572
|
-
output_text = json.dumps([r.to_dict() for r in results], indent=2, ensure_ascii=False)
|
573
|
-
if not output_path:
|
574
|
-
console.print_json(data=json.loads(output_text))
|
575
|
-
elif output_format == "markdown":
|
576
|
-
# Formata a saída em Markdown
|
577
|
-
output_lines = [
|
578
|
-
f"# Resultados da varredura de portas em {target}\n",
|
579
|
-
"| Porta | Protocolo | Status | Serviço | Versão | Vulnerabilidades |",
|
580
|
-
"|-------|-----------|--------|---------|---------|------------------|"
|
581
|
-
]
|
582
|
-
|
583
|
-
for result in results:
|
584
|
-
service = getattr(result, 'service', None)
|
585
|
-
status = "🟢 ABERTA" if getattr(result, 'status', '') == "open" else "🔴 FECHADA"
|
586
|
-
service_name = getattr(service, 'name', 'desconhecido') if service else "desconhecido"
|
587
|
-
version = getattr(service, 'version', '') if service else ""
|
588
|
-
vulns = ", ".join(service.vulns) if service and hasattr(service, 'vulns') and service.vulns else "-"
|
589
|
-
|
590
|
-
output_lines.append(
|
591
|
-
f"| {getattr(result, 'port', '')} | "
|
592
|
-
f"{getattr(result, 'protocol', 'tcp').upper()} | "
|
593
|
-
f"{status} | "
|
594
|
-
f"{service_name} | "
|
595
|
-
f"{version} | "
|
596
|
-
f"{vulns if vulns and vulns != '[]' else '-'} |"
|
597
|
-
)
|
598
|
-
|
599
|
-
# Adiciona resumo
|
600
|
-
open_ports = [r for r in results if getattr(r, 'status', '') == "open"]
|
601
|
-
output_text = "\n".join(output_lines)
|
602
|
-
output_text += f"\n\n✅ **Varredura concluída!** {len(open_ports)} porta(s) aberta(s) encontrada(s)."
|
603
|
-
|
604
|
-
if not output_path:
|
605
|
-
console.print(output_text)
|
606
|
-
else: # Formato de texto simples
|
607
|
-
# Cria tabela de resultados
|
608
|
-
table = Table(
|
609
|
-
title=f"🔍 Resultados da varredura em {target}",
|
610
|
-
box=box.ROUNDED,
|
611
|
-
header_style="bold magenta",
|
612
|
-
expand=True
|
613
|
-
)
|
614
|
-
|
615
|
-
# Adiciona colunas
|
616
|
-
table.add_column("Porta", style="red", justify="right")
|
617
|
-
table.add_column("Protocolo", style="magenta")
|
618
|
-
table.add_column("Status", style="green")
|
619
|
-
table.add_column("Serviço", style="yellow")
|
620
|
-
table.add_column("Versão", style="blue")
|
621
|
-
table.add_column("Vulnerabilidades", style="red")
|
622
|
-
|
623
|
-
# Adiciona linhas com os resultados
|
624
|
-
for result in results:
|
625
|
-
service = getattr(result, 'service', None)
|
626
|
-
status = "[green]ABERTA[/green]" if getattr(result, 'status', '') == "open" else "[red]FECHADA[/red]"
|
627
|
-
service_name = getattr(service, 'name', 'desconhecido') if service else "desconhecido"
|
628
|
-
version = getattr(service, 'version', '') if service else ""
|
629
|
-
vulns = ", ".join(service.vulns) if service and hasattr(service, 'vulns') and service.vulns else "-"
|
630
|
-
|
631
|
-
table.add_row(
|
632
|
-
str(getattr(result, 'port', '')),
|
633
|
-
getattr(result, 'protocol', 'tcp').upper(),
|
634
|
-
status,
|
635
|
-
service_name,
|
636
|
-
version,
|
637
|
-
vulns if vulns and vulns != "[]" else "-"
|
638
|
-
)
|
639
|
-
|
640
|
-
# Exibe a tabela
|
641
|
-
console.print(table)
|
642
|
-
|
643
|
-
# Resumo
|
644
|
-
open_ports = [r for r in results if getattr(r, 'status', '') == "open"]
|
645
|
-
console.print(f"\n✅ [bold green]Varredura concluída![/bold green] {len(open_ports)} porta(s) aberta(s) encontrada(s).")
|
646
|
-
|
647
|
-
# Prepara o texto para salvar em arquivo
|
648
|
-
output_text = str(table)
|
649
|
-
|
650
|
-
# Salva em arquivo se especificado
|
651
|
-
if output_path:
|
652
|
-
try:
|
653
|
-
output_path.parent.mkdir(parents=True, exist_ok=True)
|
654
|
-
with open(output_path, "w", encoding="utf-8") as f:
|
655
|
-
f.write(output_text)
|
656
|
-
console.print(f"\n💾 [bold]Resultados salvos em:[/bold] [red]{output_path.absolute()}[/red]")
|
657
|
-
except Exception as e:
|
658
|
-
console.print(f"\n❌ [bold red]Erro ao salvar arquivo:[/bold red] {str(e)}")
|
659
|
-
|
660
|
-
except KeyboardInterrupt:
|
661
|
-
console.print("\n❌ [bold red]Varredura cancelada pelo usuário[/bold red]")
|
662
|
-
raise typer.Exit(1)
|
663
|
-
except Exception as e:
|
664
|
-
console.print(f"\n❌ [bold red]Erro durante a varredura:[/bold red] {str(e)}")
|
665
|
-
if "Errno 8" in str(e) or "Name or service not known" in str(e):
|
666
|
-
console.print("💡 [yellow]Dica:[/yellow] Não foi possível resolver o nome do host. Verifique a conexão com a internet ou o nome do domínio.")
|
667
|
-
if "No module named 'nmap'" in str(e):
|
668
|
-
console.print("💡 [yellow]Dica:[/yellow] O módulo 'python-nmap' não está instalado. Instale com: [red]pip install python-nmap[/red]")
|
669
|
-
raise typer.Exit(1)
|
670
|
-
|
671
|
-
async def run_port_scan(
|
672
|
-
target: str,
|
673
|
-
profile: str,
|
674
|
-
stealth: int,
|
675
|
-
concurrency: int,
|
676
|
-
timeout: float,
|
677
|
-
output: Optional[str],
|
678
|
-
format: str,
|
679
|
-
):
|
680
|
-
"""Função assíncrona que executa a varredura de portas."""
|
681
|
-
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn, TaskProgressColumn, TimeElapsedColumn
|
682
|
-
from rich.panel import Panel
|
683
|
-
from rich.table import Table
|
684
|
-
from pathlib import Path
|
685
|
-
import json
|
686
|
-
|
687
|
-
# Valida os parâmetros
|
688
|
-
if stealth < 0 or stealth > 5:
|
689
|
-
console.print("[red]Erro:[/red] O nível de stealth deve estar entre 0 e 5")
|
690
|
-
raise typer.Exit(1)
|
691
|
-
|
692
|
-
if profile not in PROFILES:
|
693
|
-
console.print(f"[red]Erro:[/red] Perfil inválido. Use um dos seguintes: {', '.join(PROFILES.keys())}")
|
694
|
-
raise typer.Exit(1)
|
695
|
-
|
696
|
-
# Usa o perfil especificado ou o padrão 'quick'
|
697
|
-
ports = PROFILES.get(profile.lower(), PROFILES["quick"])
|
698
|
-
|
699
|
-
# Configura o arquivo de saída
|
700
|
-
output_path = Path(output) if output else None
|
701
|
-
if output_path:
|
702
|
-
if output_path.suffix not in (".json", ".txt", ".md"):
|
703
|
-
console.print("[yellow]Aviso:[/yellow] Extensão de arquivo não suportada. Usando .json")
|
704
|
-
output_path = output_path.with_suffix(".json")
|
705
|
-
|
706
|
-
# Configura a barra de progresso
|
707
|
-
progress_columns = [
|
708
|
-
SpinnerColumn(),
|
709
|
-
TextColumn("[progress.description]{task.description}"),
|
710
|
-
BarColumn(bar_width=None),
|
711
|
-
TaskProgressColumn(),
|
712
|
-
TimeElapsedColumn(),
|
713
|
-
]
|
714
|
-
|
715
|
-
# Cabeçalho da varredura
|
716
|
-
console.rule(f"🔍 [bold red]Varredura de Portas[/bold red]")
|
717
|
-
|
718
|
-
# Tabela de informações
|
719
|
-
info_table = Table.grid(padding=(0, 1))
|
720
|
-
info_table.add_row("🌐 Alvo:", f"[bold]{target}")
|
721
|
-
info_table.add_row("🔢 Portas:", f"[red]{ports}")
|
722
|
-
info_table.add_row("🛡️ Nível de stealth:", f"[yellow]{stealth}")
|
723
|
-
info_table.add_row("🔍 Detecção de serviços:", "[green]Ativada[/green]")
|
724
|
-
info_table.add_row("⚠️ Verificação de vulnerabilidades:", "[green]Ativada[/green]")
|
725
|
-
|
726
|
-
console.print(Panel(info_table, title="[bold]Configuração da Varredura[/bold]", border_style="red"))
|
727
|
-
console.print()
|
728
|
-
|
729
|
-
# Executa a varredura com barra de progresso
|
730
|
-
with Progress(*progress_columns) as progress:
|
731
|
-
task = progress.add_task("[red]Escaneando portas...", total=100)
|
732
|
-
|
733
|
-
try:
|
734
|
-
# Cria o scanner Nmap
|
735
|
-
scanner = PortScanner(
|
736
|
-
target=target,
|
737
|
-
ports=ports,
|
738
|
-
scan_type="syn" if stealth < 2 else "tcp",
|
739
|
-
stealth_level=stealth,
|
740
|
-
resolve_services=True,
|
741
|
-
check_vulns=True,
|
742
|
-
)
|
743
|
-
|
744
|
-
# Executa o scan de forma assíncrona
|
745
|
-
results = await scanner.scan()
|
746
|
-
progress.update(task, completed=100)
|
747
|
-
|
748
|
-
if not results:
|
749
|
-
console.print("\n[yellow]⚠️ Nenhuma porta aberta detectada[/yellow]")
|
750
|
-
return
|
751
|
-
|
752
|
-
# Formata a saída
|
753
|
-
output_format = format.lower()
|
754
|
-
|
755
|
-
if output_format == "json":
|
756
|
-
output_text = json.dumps([r.to_dict() for r in results], indent=2, ensure_ascii=False)
|
757
|
-
if not output_path:
|
758
|
-
console.print_json(data=json.loads(output_text))
|
759
|
-
elif output_format == "markdown":
|
760
|
-
output_lines = [
|
761
|
-
f"# Resultados da varredura de portas em {target}\n",
|
762
|
-
"| Porta | Protocolo | Status | Serviço | Versão |",
|
763
|
-
"|-------|-----------|--------|---------|---------|"
|
764
|
-
]
|
765
|
-
|
766
|
-
for result in results:
|
767
|
-
service = getattr(result, 'service', None)
|
768
|
-
status = "🟢 ABERTA" if getattr(result, 'status', '') == "open" else "🔴 FECHADA"
|
769
|
-
service_name = getattr(service, 'name', 'desconhecido') if service else "desconhecido"
|
770
|
-
version = getattr(service, 'version', '') if service else ""
|
771
|
-
|
772
|
-
output_lines.append(
|
773
|
-
f"| {getattr(result, 'port', '')} | "
|
774
|
-
f"{getattr(result, 'protocol', 'tcp').upper()} | "
|
775
|
-
f"{status} | "
|
776
|
-
f"{service_name} | "
|
777
|
-
f"{version} |"
|
778
|
-
)
|
779
|
-
|
780
|
-
# Adiciona resumo
|
781
|
-
open_ports = [r for r in results if getattr(r, 'status', '') == "open"]
|
782
|
-
output_text = "\n".join(output_lines)
|
783
|
-
output_text += f"\n\n✅ **Varredura concluída!** {len(open_ports)} porta(s) aberta(s) encontrada(s)."
|
784
|
-
|
785
|
-
if not output_path:
|
786
|
-
console.print(output_text)
|
787
|
-
else: # Formato de texto simples
|
788
|
-
# Cria tabela de resultados
|
789
|
-
table = Table(
|
790
|
-
title=f"🔍 Resultados da varredura em {target}",
|
791
|
-
box=box.ROUNDED,
|
792
|
-
header_style="bold magenta",
|
793
|
-
expand=True
|
794
|
-
)
|
795
|
-
|
796
|
-
# Adiciona colunas
|
797
|
-
table.add_column("Porta", style="red", justify="right")
|
798
|
-
table.add_column("Protocolo", style="magenta")
|
799
|
-
table.add_column("Status", style="green")
|
800
|
-
table.add_column("Serviço", style="yellow")
|
801
|
-
table.add_column("Versão", style="blue")
|
802
|
-
|
803
|
-
# Adiciona linhas com os resultados
|
804
|
-
for result in results:
|
805
|
-
service = getattr(result, 'service', None)
|
806
|
-
status = "[green]ABERTA[/green]" if getattr(result, 'status', '') == "open" else "[red]FECHADA[/red]"
|
807
|
-
service_name = getattr(service, 'name', 'desconhecido') if service else "desconhecido"
|
808
|
-
version = getattr(service, 'version', '') if service else ""
|
809
|
-
|
810
|
-
table.add_row(
|
811
|
-
str(getattr(result, 'port', '')),
|
812
|
-
getattr(result, 'protocol', 'tcp').upper(),
|
813
|
-
status,
|
814
|
-
service_name,
|
815
|
-
version,
|
816
|
-
)
|
817
|
-
|
818
|
-
# Exibe a tabela
|
819
|
-
console.print(table)
|
820
|
-
|
821
|
-
# Resumo
|
822
|
-
open_ports = [r for r in results if getattr(r, 'status', '') == "open"]
|
823
|
-
console.print(f"\n✅ [bold green]Varredura concluída![/bold green] {len(open_ports)} porta(s) aberta(s) encontrada(s).")
|
824
|
-
|
825
|
-
# Prepara o texto para salvar em arquivo
|
826
|
-
output_text = str(table)
|
827
|
-
|
828
|
-
# Salva em arquivo se especificado
|
829
|
-
if output_path:
|
830
|
-
try:
|
831
|
-
output_path.parent.mkdir(parents=True, exist_ok=True)
|
832
|
-
with open(output_path, "w", encoding="utf-8") as f:
|
833
|
-
f.write(output_text)
|
834
|
-
console.print(f"\n💾 [bold]Resultados salvos em:[/bold] [red]{output_path.absolute()}[/red]")
|
835
|
-
except Exception as e:
|
836
|
-
console.print(f"\n❌ [bold red]Erro ao salvar arquivo:[/bold red] {str(e)}")
|
837
|
-
|
838
|
-
except KeyboardInterrupt:
|
839
|
-
console.print("\n❌ [bold red]Varredura cancelada pelo usuário[/bold red]")
|
840
|
-
raise typer.Exit(1)
|
841
|
-
except Exception as e:
|
842
|
-
console.print(f"\n❌ [bold red]Erro durante a varredura:[/bold red] {str(e)}")
|
843
|
-
if "Errno 8" in str(e) or "Name or service not known" in str(e):
|
844
|
-
console.print("💡 [yellow]Dica:[/yellow] Não foi possível resolver o nome do host. Verifique a conexão com a internet ou o nome do domínio.")
|
845
|
-
if "No module named 'nmap'" in str(e):
|
846
|
-
console.print("💡 [yellow]Dica:[/yellow] O módulo 'python-nmap' não está instalado. Instale com: [red]pip install python-nmap[/red]")
|
847
|
-
raise typer.Exit(1)
|
848
|
-
|
849
507
|
@app.command("ports")
|
850
508
|
def port_scan(
|
851
509
|
target: str = typer.Argument(..., help="Domínio ou IP para escanear"),
|
@@ -903,7 +561,7 @@ def port_scan(
|
|
903
561
|
),
|
904
562
|
):
|
905
563
|
"""
|
906
|
-
🔍 Varredura avançada de portas com detecção de serviços e vulnerabilidades
|
564
|
+
🔍 Varredura avançada de portas com detecção de serviços e vulnerabilidades.
|
907
565
|
|
908
566
|
Exemplos:
|
909
567
|
moriarty domain ports example.com
|
@@ -912,218 +570,84 @@ def port_scan(
|
|
912
570
|
"""
|
913
571
|
import asyncio
|
914
572
|
import json
|
915
|
-
import os
|
916
|
-
import sys
|
917
573
|
from pathlib import Path
|
918
|
-
from
|
919
|
-
|
920
|
-
|
921
|
-
|
922
|
-
|
923
|
-
|
924
|
-
|
925
|
-
|
926
|
-
|
927
|
-
|
928
|
-
from rich.panel import Panel
|
929
|
-
from rich.table import Table, box
|
930
|
-
from rich.console import Console
|
931
|
-
|
932
|
-
from moriarty.modules.port_scanner_nmap import PortScanner, PortScanResult, ServiceInfo
|
933
|
-
|
934
|
-
# Mapeia os perfis para o formato do Nmap
|
935
|
-
port_profiles = {
|
936
|
-
"quick": "21-23,25,53,80,110,111,135,139,143,389,443,445,465,587,993,995,1433,1521,2049,3306,3389,5432,5900,6379,8080,8443,9000,10000,27017",
|
937
|
-
"web": "80,443,8080,8443,8000,8888,10443,4443",
|
938
|
-
"mail": "25,110,143,465,587,993,995",
|
939
|
-
"db": "1433,1521,27017-27019,28017,3306,5000,5432,5984,6379,8081",
|
940
|
-
"full": "1-1024",
|
941
|
-
"all": "1-65535",
|
942
|
-
}
|
943
|
-
|
944
|
-
# Verifica se o Nmap está instalado
|
945
|
-
def check_nmap_installed() -> bool:
|
946
|
-
"""Verifica se o Nmap está instalado no sistema."""
|
947
|
-
try:
|
948
|
-
import nmap
|
949
|
-
return True
|
950
|
-
except ImportError:
|
951
|
-
return False
|
952
|
-
|
953
|
-
# Valida os parâmetros de entrada
|
954
|
-
def validate_parameters() -> None:
|
955
|
-
"""Valida os parâmetros de entrada."""
|
956
|
-
if not check_nmap_installed():
|
957
|
-
console.print("❌ [bold red]Erro:[/bold red] O módulo 'python-nmap' não está instalado.")
|
958
|
-
console.print("💡 [yellow]Dica:[/yellow] Instale com: pip install python-nmap")
|
959
|
-
raise typer.Exit(1)
|
960
|
-
|
961
|
-
if stealth < 0 or stealth > 5:
|
962
|
-
console.print("❌ [bold red]Erro:[/bold red] O nível de stealth deve estar entre 0 e 5.")
|
963
|
-
raise typer.Exit(1)
|
964
|
-
|
965
|
-
# Inicializa o console do Rich
|
966
|
-
console = Console()
|
967
|
-
|
968
|
-
# Valida os parâmetros
|
969
|
-
validate_parameters()
|
970
|
-
|
971
|
-
# Usa o perfil especificado ou o padrão 'quick'
|
972
|
-
ports = port_profiles.get(profile.lower(), port_profiles["quick"])
|
973
|
-
|
974
|
-
# Configura o arquivo de saída
|
975
|
-
output_path = Path(output) if output else None
|
976
|
-
if output_path:
|
977
|
-
if output_path.suffix not in (".json", ".txt", ".md"):
|
574
|
+
from moriarty.modules.port_scanner import format_scan_results
|
575
|
+
|
576
|
+
# Validações
|
577
|
+
if profile not in PROFILES:
|
578
|
+
console.print(f"[red]Erro:[/red] Perfil inválido. Use um destes: {', '.join(PROFILES.keys())}")
|
579
|
+
raise typer.Exit(1)
|
580
|
+
|
581
|
+
if output:
|
582
|
+
output = Path(output)
|
583
|
+
if output.suffix not in (".json", ".txt", ".md"):
|
978
584
|
console.print("[yellow]Aviso:[/yellow] Extensão de arquivo não suportada. Usando .json")
|
979
|
-
|
980
|
-
|
981
|
-
|
982
|
-
progress_columns = [
|
983
|
-
SpinnerColumn(),
|
984
|
-
TextColumn("[progress.description]{task.description}"),
|
985
|
-
BarColumn(bar_width=None),
|
986
|
-
TaskProgressColumn(),
|
987
|
-
TimeElapsedColumn(),
|
988
|
-
]
|
989
|
-
|
990
|
-
# Cabeçalho da varredura
|
991
|
-
console.rule(f"🔍 [bold red]Varredura de Portas[/bold red]")
|
992
|
-
|
993
|
-
# Tabela de informações
|
994
|
-
info_table = Table.grid(padding=(0, 1))
|
995
|
-
info_table.add_row("🌐 Alvo:", f"[bold]{target}")
|
996
|
-
info_table.add_row("🔢 Portas:", f"[red]{ports}")
|
997
|
-
info_table.add_row("🛡️ Nível de stealth:", f"[yellow]{stealth}")
|
998
|
-
info_table.add_row("🔍 Detecção de serviços:", "[green]Ativada[/green]" if resolve_services else "[red]Desativada[/red]")
|
999
|
-
info_table.add_row("⚠️ Verificação de vulnerabilidades:", "[green]Ativada[/green]" if check_vulns else "[red]Desativada[/red]")
|
1000
|
-
|
1001
|
-
console.print(Panel(info_table, title="[bold]Configuração da Varredura[/bold]", border_style="red"))
|
1002
|
-
console.print()
|
1003
|
-
|
1004
|
-
async def run_scan() -> List[PortScanResult]:
|
1005
|
-
"""Executa a varredura de portas."""
|
1006
|
-
# Usa sempre TCP Connect que não requer privilégios de root
|
1007
|
-
console.print("[yellow]Aviso:[/yellow] Usando varredura TCP Connect (não requer privilégios de root).")
|
1008
|
-
scan_type = "tcp"
|
1009
|
-
|
1010
|
-
# Cria o scanner Nmap
|
585
|
+
output = output.with_suffix(".json")
|
586
|
+
|
587
|
+
async def _run():
|
1011
588
|
scanner = PortScanner(
|
1012
589
|
target=target,
|
1013
|
-
|
1014
|
-
|
590
|
+
profile=profile,
|
591
|
+
concurrency=concurrency,
|
592
|
+
timeout=timeout,
|
1015
593
|
stealth_level=stealth,
|
1016
594
|
resolve_services=resolve_services,
|
1017
595
|
check_vulns=check_vulns,
|
1018
596
|
)
|
1019
|
-
|
1020
|
-
# Executa o scan e retorna os resultados
|
1021
597
|
return await scanner.scan()
|
1022
598
|
|
1023
|
-
|
599
|
+
# Executa o scanner
|
600
|
+
console.print(f"[bold]🔍 Iniciando varredura em:[/bold] {target}")
|
601
|
+
console.print(f"📊 Perfil: {profile} (portas: {len(PROFILES[profile])})")
|
602
|
+
console.print(f"🕵️ Nível de stealth: {stealth}")
|
1024
603
|
|
1025
|
-
|
1026
|
-
|
1027
|
-
output_format = format.lower()
|
604
|
+
try:
|
605
|
+
results = asyncio.run(_run())
|
1028
606
|
|
1029
|
-
|
1030
|
-
|
1031
|
-
|
1032
|
-
|
607
|
+
if not results:
|
608
|
+
console.print("[yellow]⚠️ Nenhuma porta aberta detectada[/yellow]")
|
609
|
+
return
|
610
|
+
|
611
|
+
# Formata a saída
|
612
|
+
if format.lower() == "json":
|
613
|
+
output_text = json.dumps([r.to_dict() for r in results], indent=2)
|
614
|
+
if not output:
|
615
|
+
console.print_json(data=json.loads(output_text))
|
616
|
+
else:
|
617
|
+
output_text = format_scan_results(results, output_format=format)
|
618
|
+
if not output:
|
619
|
+
console.print(output_text)
|
620
|
+
|
621
|
+
# Salva em arquivo se especificado
|
622
|
+
if output:
|
623
|
+
output.parent.mkdir(parents=True, exist_ok=True)
|
624
|
+
with open(output, "w", encoding="utf-8") as f:
|
625
|
+
if output.suffix == ".json":
|
626
|
+
json.dump([r.to_dict() for r in results], f, indent=2, ensure_ascii=False)
|
627
|
+
else:
|
1033
628
|
f.write(output_text)
|
1034
|
-
|
1035
|
-
with open(output_path, 'w', encoding='utf-8') as f:
|
1036
|
-
f.write("# Resultados da Varredura de Portas\n\n")
|
1037
|
-
f.write("| Porta | Estado | Serviço | Versão |\n")
|
1038
|
-
f.write("|-------|--------|---------|---------|\n")
|
1039
|
-
for result in results:
|
1040
|
-
if result.state == 'open':
|
1041
|
-
service = result.service or "desconhecido"
|
1042
|
-
version = result.version or ""
|
1043
|
-
f.write(f"| {result.port} | ABERTA | {service} | {version} |\n")
|
1044
|
-
else: # text
|
1045
|
-
with open(output_path, 'w', encoding='utf-8') as f:
|
1046
|
-
for result in results:
|
1047
|
-
if result.state == 'open':
|
1048
|
-
service = result.service or "desconhecido"
|
1049
|
-
version = f" ({result.version})" if result.version else ""
|
1050
|
-
f.write(f"Porta {result.port}/tcp: ABERTA - {service}{version}\n")
|
1051
|
-
except Exception as e:
|
1052
|
-
console.print(f"❌ [bold red]Erro ao salvar o arquivo:[/bold red] {str(e)}")
|
1053
|
-
raise typer.Exit(1)
|
629
|
+
console.print(f"\n[green]✅ Resultados salvos em:[/green] {output.absolute()}")
|
1054
630
|
|
1055
|
-
|
1056
|
-
|
1057
|
-
|
1058
|
-
|
1059
|
-
|
1060
|
-
|
1061
|
-
results = asyncio.run(run_scan())
|
1062
|
-
progress.update(task, completed=100)
|
1063
|
-
|
1064
|
-
# Exibe os resultados
|
1065
|
-
if results:
|
1066
|
-
# Cria uma tabela para exibir os resultados
|
1067
|
-
table = Table(title="[bold]Resultados da Varredura[/bold]")
|
1068
|
-
table.add_column("Porta", style="red", no_wrap=True)
|
1069
|
-
table.add_column("Estado", style="green")
|
1070
|
-
table.add_column("Serviço", style="yellow")
|
1071
|
-
table.add_column("Versão", style="magenta")
|
1072
|
-
|
1073
|
-
for result in results:
|
1074
|
-
if result.state == 'open':
|
1075
|
-
service = result.service or "desconhecido"
|
1076
|
-
version = result.version or ""
|
1077
|
-
table.add_row(
|
1078
|
-
str(result.port),
|
1079
|
-
"[green]ABERTA[/green]",
|
1080
|
-
service,
|
1081
|
-
version
|
1082
|
-
)
|
1083
|
-
|
1084
|
-
console.print()
|
1085
|
-
console.print(table)
|
1086
|
-
|
1087
|
-
# Salva os resultados em um arquivo, se solicitado
|
1088
|
-
if output_path:
|
1089
|
-
save_results(results, output_path, format)
|
1090
|
-
console.print(f"\n✅ Resultados salvos em: [bold red]{output_path}[/bold red]")
|
1091
|
-
|
1092
|
-
# Exibe saída JSON no console se não houver arquivo de saída e o formato for JSON
|
1093
|
-
if format.lower() == "json" and not output_path:
|
1094
|
-
output_text = json.dumps([r.to_dict() for r in results], indent=2, ensure_ascii=False)
|
1095
|
-
console.print_json(data=json.loads(output_text))
|
1096
|
-
|
1097
|
-
else:
|
1098
|
-
console.print("\n❌ Nenhuma porta aberta encontrada ou ocorreu um erro durante a varredura.")
|
1099
|
-
|
1100
|
-
except Exception as e:
|
1101
|
-
progress.update(task, completed=100)
|
1102
|
-
console.print(f"\n❌ [bold red]Erro durante a varredura:[/bold red] {str(e)}")
|
1103
|
-
if "Errno 8" in str(e) or "Name or service not known" in str(e):
|
1104
|
-
console.print("💡 [yellow]Dica:[/yellow] Não foi possível resolver o nome do host. Verifique a conexão com a internet ou o nome do domínio.")
|
1105
|
-
raise typer.Exit(1)
|
631
|
+
except Exception as e:
|
632
|
+
console.print(f"[red]❌ Erro durante a varredura:[/red] {str(e)}")
|
633
|
+
if "object NoneType can't be used in 'await' expression" in str(e):
|
634
|
+
console.print("[yellow]Dica:[/yellow] Tente aumentar o timeout com --timeout 5.0")
|
635
|
+
raise typer.Exit(1)
|
636
|
+
console.print(f"[green]✓ Resultado salvo em[/green] {output}")
|
1106
637
|
|
1107
638
|
|
1108
639
|
@app.command("crawl")
|
1109
640
|
def crawl(
|
1110
641
|
domain: str = typer.Argument(..., help="Alvo base (ex: https://example.com)"),
|
642
|
+
max_pages: int = typer.Option(100, "--max-pages", help="Máximo de páginas"),
|
1111
643
|
max_depth: int = typer.Option(2, "--max-depth", help="Profundidade máxima"),
|
1112
|
-
max_pages: int = typer.Option(100, "--max-pages", help="Número máximo de páginas a serem rastreadas"),
|
1113
644
|
concurrency: int = typer.Option(10, "--concurrency", help="Workers paralelos"),
|
1114
645
|
follow_subdomains: bool = typer.Option(False, "--follow-subdomains", help="Seguir subdomínios"),
|
1115
646
|
output: Optional[str] = typer.Option(None, "--output", "-o", help="Arquivo JSON de saída"),
|
1116
647
|
):
|
1117
|
-
"""Crawler leve para mapear formulários e rotas.
|
1118
|
-
|
1119
|
-
Exemplos:
|
1120
|
-
moriarty domain crawl https://example.com
|
1121
|
-
moriarty domain crawl https://example.com --max-depth 3 --max-pages 50
|
1122
|
-
"""
|
648
|
+
"""Crawler leve para mapear formulários e rotas."""
|
1123
649
|
import asyncio
|
1124
650
|
import json
|
1125
|
-
from typing import Dict, Any
|
1126
|
-
from pathlib import Path
|
1127
651
|
|
1128
652
|
async def _run():
|
1129
653
|
crawler = WebCrawler(
|
@@ -1138,46 +662,15 @@ def crawl(
|
|
1138
662
|
finally:
|
1139
663
|
await crawler.close()
|
1140
664
|
|
1141
|
-
|
1142
|
-
|
1143
|
-
|
1144
|
-
|
1145
|
-
|
1146
|
-
|
1147
|
-
for url, page in pages.items():
|
1148
|
-
safe_pages[url] = {
|
1149
|
-
'url': url,
|
1150
|
-
'title': getattr(page, 'title', ''),
|
1151
|
-
'forms': [form.__dict__ for form in getattr(page, 'forms', [])],
|
1152
|
-
'links': getattr(page, 'links', []),
|
1153
|
-
'status_code': getattr(page, 'status_code', 0)
|
1154
|
-
}
|
1155
|
-
|
1156
|
-
# Exibe resumo
|
1157
|
-
summary = {
|
1158
|
-
"total_pages": len(pages),
|
1159
|
-
"forms": sum(len(page.get('forms', [])) for page in safe_pages.values()),
|
1160
|
-
"links_unicos": len({link for page in safe_pages.values() for link in page.get('links', [])})
|
1161
|
-
}
|
1162
|
-
|
1163
|
-
console.print("\n📊 [bold]Resumo do Crawling:[/bold]")
|
1164
|
-
console.print(f"• Páginas processadas: {summary['total_pages']}")
|
1165
|
-
console.print(f"• Formulários encontrados: {summary['forms']}")
|
1166
|
-
console.print(f"• Links únicos: {summary['links_unicos']}")
|
665
|
+
pages = asyncio.run(_run())
|
666
|
+
summary = {
|
667
|
+
"total_pages": len(pages),
|
668
|
+
"forms": sum(len(page.forms) for page in pages.values()),
|
669
|
+
}
|
670
|
+
console.print_json(data=summary)
|
1167
671
|
|
1168
|
-
|
1169
|
-
|
1170
|
-
|
1171
|
-
|
1172
|
-
|
1173
|
-
json.dump({
|
1174
|
-
"summary": summary,
|
1175
|
-
"pages": safe_pages
|
1176
|
-
}, f, indent=2, ensure_ascii=False)
|
1177
|
-
console.print(f"\n💾 [bold]Resultados salvos em:[/bold] [red]{output_path.absolute()}[/red]")
|
1178
|
-
|
1179
|
-
except Exception as e:
|
1180
|
-
console.print(f"\n❌ [bold red]Erro durante o crawling:[/bold red] {str(e)}")
|
1181
|
-
if "Failed to establish a new connection" in str(e):
|
1182
|
-
console.print("💡 [yellow]Dica:[/yellow] Verifique sua conexão com a internet ou a URL fornecida.")
|
1183
|
-
raise typer.Exit(1)
|
672
|
+
if output:
|
673
|
+
payload = {url: page.__dict__ for url, page in pages.items()}
|
674
|
+
with open(output, "w", encoding="utf-8") as handle:
|
675
|
+
json.dump(payload, handle, indent=2, ensure_ascii=False)
|
676
|
+
console.print(f"[green]✓ Resultado salvo em[/green] {output}")
|