forensic-cli 1.2.5__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.
Files changed (44) hide show
  1. cli/__init__.py +0 -0
  2. cli/commands/__init__.py +0 -0
  3. cli/commands/browser.py +220 -0
  4. cli/commands/describe.py +54 -0
  5. cli/commands/email.py +3 -0
  6. cli/commands/network.py +126 -0
  7. cli/commands/utils.py +3 -0
  8. cli/main.py +18 -0
  9. core/__init__.py +0 -0
  10. core/browser/browser_history.py +156 -0
  11. core/browser/common_words.py +60 -0
  12. core/browser/downloads_history.py +129 -0
  13. core/browser/fav_screen.py +70 -0
  14. core/browser/logins.py +108 -0
  15. core/browser/unusual_patterns.py +111 -0
  16. core/data/__init__.py +0 -0
  17. core/data/data_recovery.py +157 -0
  18. core/db/db.py +12 -0
  19. core/email/email_parser.py +87 -0
  20. core/email/header_analysis.py +87 -0
  21. core/models/orm.py +48 -0
  22. core/models/schemas.py +76 -0
  23. core/network/__init__.py +0 -0
  24. core/network/arp_scan.py +16 -0
  25. core/network/dns_recon.py +90 -0
  26. core/network/fingerprinting.py +117 -0
  27. core/network/ip_info.py +29 -0
  28. core/network/network_map.py +120 -0
  29. core/network/ping_sweep.py +53 -0
  30. core/network/port_scanner.py +93 -0
  31. core/network/smb_scan.py +32 -0
  32. core/network/snmp_scan.py +43 -0
  33. core/network/traceroute.py +40 -0
  34. core/network/utils.py +27 -0
  35. core/registry.py +25 -0
  36. core/utils/__init__.py +0 -0
  37. core/utils/gerar_amostras.py +33 -0
  38. forensic_cli-1.2.5.dist-info/METADATA +293 -0
  39. forensic_cli-1.2.5.dist-info/RECORD +44 -0
  40. forensic_cli-1.2.5.dist-info/WHEEL +5 -0
  41. forensic_cli-1.2.5.dist-info/entry_points.txt +2 -0
  42. forensic_cli-1.2.5.dist-info/licenses/LICENSE +21 -0
  43. forensic_cli-1.2.5.dist-info/licenses/LICENSE-GPL +675 -0
  44. forensic_cli-1.2.5.dist-info/top_level.txt +2 -0
cli/__init__.py ADDED
File without changes
File without changes
@@ -0,0 +1,220 @@
1
+ import os
2
+ import json
3
+ import typer
4
+ from pathlib import Path
5
+
6
+ from core.browser.browser_history import extract_google_edge_history, extract_firefox_history
7
+ from core.browser.downloads_history import extract_downloads_history
8
+ from core.browser.fav_screen import carregar_json, extrair_urls_validas, processar_urls
9
+ from core.browser.unusual_patterns import processar_historico_da_pasta
10
+ from core.browser.logins import collect_chrome_logins, collect_edge_logins
11
+ from core.browser.common_words import extract_words
12
+
13
+ browser_app = typer.Typer()
14
+
15
+ @browser_app.command("history")
16
+ def history(
17
+ chrome: bool = typer.Option(False, "--chrome", help="Extrair histórico do Chrome"),
18
+ edge: bool = typer.Option(False, "--edge", help="Extrair histórico do Edge"),
19
+ firefox: bool = typer.Option(False, "--firefox", help="Extrair histórico do Firefox"),
20
+ all: bool = typer.Option(False, "--all", help="Extrair histórico de todos navegadores")
21
+ ):
22
+ usuario = os.getlogin()
23
+ home = str(Path.home())
24
+
25
+ if all or chrome:
26
+ caminho_chrome = os.path.join(home, "AppData", "Local", "Google", "Chrome", "User Data", "Default", "History")
27
+ if os.path.exists(caminho_chrome):
28
+ typer.echo("[*] Extraindo histórico do Chrome...")
29
+ extract_google_edge_history(caminho_chrome, "Chrome", usuario)
30
+ else:
31
+ typer.echo("[!] Chrome não encontrado.")
32
+
33
+ if all or edge:
34
+ caminho_edge = os.path.join(home, "AppData", "Local", "Microsoft", "Edge", "User Data", "Default", "History")
35
+ if os.path.exists(caminho_edge):
36
+ typer.echo("[*] Extraindo histórico do Edge...")
37
+ extract_google_edge_history(caminho_edge, "Edge", usuario)
38
+ else:
39
+ typer.echo("[!] Edge não encontrado.")
40
+
41
+ if all or firefox:
42
+ caminho_firefox_perfis = os.path.join(home, "AppData", "Roaming", "Mozilla", "Firefox", "Profiles")
43
+ if os.path.exists(caminho_firefox_perfis):
44
+ for perfil in os.listdir(caminho_firefox_perfis):
45
+ caminho_sqlite = os.path.join(caminho_firefox_perfis, perfil, "places.sqlite")
46
+ if os.path.exists(caminho_sqlite):
47
+ typer.echo(f"[*] Extraindo histórico do Firefox ({perfil})...")
48
+ extract_firefox_history(caminho_sqlite, usuario)
49
+ break
50
+ else:
51
+ typer.echo("[!] places.sqlite do Firefox não encontrado.")
52
+ else:
53
+ typer.echo("[!] Perfis do Firefox não encontrados.")
54
+
55
+ @browser_app.command("downloads")
56
+ def downloads(
57
+ output_dir: Path = typer.Option(
58
+ Path("artefatos/downloads"),
59
+ "--output-dir",
60
+ "-o",
61
+ help="Diretório de saída para salvar os artefatos dos downloads.",
62
+ resolve_path=True
63
+ ),
64
+ chrome: bool = typer.Option(False, "--chrome", help="Extrair downloads do Chrome"),
65
+ edge: bool = typer.Option(False, "--edge", help="Extrair downloads do Edge"),
66
+ firefox: bool = typer.Option(False, "--firefox", help="Extrair downloads do Firefox"),
67
+ all: bool = typer.Option(False, "--all", help="Extrair downloads de todos os navegadores")
68
+ ):
69
+ output_dir.mkdir(parents=True, exist_ok=True)
70
+ typer.echo(f"📂 Salvando artefatos em: {output_dir}")
71
+
72
+ navegadores = {
73
+ "chrome": chrome,
74
+ "edge": edge,
75
+ "firefox": firefox
76
+ }
77
+
78
+ if all or not any(navegadores.values()):
79
+ navegadores = {k: True for k in navegadores}
80
+
81
+ extract_downloads_history(
82
+ output_dir=output_dir,
83
+ chrome=navegadores["chrome"],
84
+ edge=navegadores["edge"],
85
+ firefox=navegadores["firefox"]
86
+ )
87
+
88
+ typer.echo("✅ Extração concluída!")
89
+
90
+ @browser_app.command("favscreen")
91
+ def favscreen(
92
+ input_dir: Path = typer.Option(
93
+ Path("artefatos/historico"),
94
+ "--input-dir",
95
+ "-i",
96
+ help="Diretório contendo os JSONs de histórico para processar.",
97
+ exists=True,
98
+ file_okay=False,
99
+ dir_okay=True,
100
+ resolve_path=True
101
+ ),
102
+ output_dir: Path = typer.Option(
103
+ Path("artefatos/favscreen"),
104
+ "--output-dir",
105
+ "-o",
106
+ help="Diretório de saída para salvar favicons e prints.",
107
+ exists=False,
108
+ file_okay=False,
109
+ dir_okay=True,
110
+ resolve_path=True
111
+ )
112
+ ):
113
+ try:
114
+ arquivos_json = [input_dir / arquivo for arquivo in os.listdir(input_dir) if arquivo.endswith(".json")]
115
+ todas_urls = []
116
+
117
+ for caminho in arquivos_json:
118
+ print(f"[INFO] Carregando: {caminho}")
119
+ dados = carregar_json(caminho)
120
+ urls = extrair_urls_validas(dados)
121
+ todas_urls.extend(urls)
122
+
123
+ if todas_urls:
124
+ todas_urls = list(set(todas_urls))
125
+ processar_urls(todas_urls, output_dir)
126
+ typer.echo("\n✅ Processamento finalizado.")
127
+ else:
128
+ typer.echo("\n⚠️ Nenhum arquivo JSON válido encontrado ou sem URLs úteis.")
129
+
130
+ except Exception as erro:
131
+ typer.echo(f"\n❌ Erro geral: {erro}")
132
+
133
+ @browser_app.command("logins")
134
+ def logins(
135
+ chrome: bool = typer.Option(False, "--chrome", help="Extrair logins do Chrome"),
136
+ edge: bool = typer.Option(False, "--edge", help="Extrair logins do Edge"),
137
+ all: bool = typer.Option(False, "--all", help="Extrair logins de todos os navegadores"),
138
+ output_dir: Path = typer.Option(
139
+ Path("artefatos/logins"),
140
+ "--output-dir",
141
+ "-o",
142
+ help="Diretório para salvar os logins em JSON",
143
+ resolve_path=True
144
+ )
145
+ ):
146
+ output_dir.mkdir(parents=True, exist_ok=True)
147
+
148
+ navegadores = {
149
+ "chrome": chrome,
150
+ "edge": edge
151
+ }
152
+
153
+ if all or not any(navegadores.values()):
154
+ navegadores = {k: True for k in navegadores}
155
+
156
+ if navegadores["chrome"]:
157
+ typer.echo("[*] Extraindo logins do Chrome...")
158
+ data_chrome = collect_chrome_logins()
159
+ arquivo_chrome = output_dir / "chrome_logins.json"
160
+ with open(arquivo_chrome, "w", encoding="utf-8") as f:
161
+ json.dump(data_chrome, f, indent=2, ensure_ascii=False)
162
+ typer.echo(f"✅ Logins do Chrome salvos em: {arquivo_chrome}")
163
+
164
+ if navegadores["edge"]:
165
+ typer.echo("[*] Extraindo logins do Edge...")
166
+ data_edge = collect_edge_logins()
167
+ arquivo_edge = output_dir / "edge_logins.json"
168
+ with open(arquivo_edge, "w", encoding="utf-8") as f:
169
+ json.dump(data_edge, f, indent=2, ensure_ascii=False)
170
+ typer.echo(f"✅ Logins do Edge salvos em: {arquivo_edge}")
171
+
172
+ @browser_app.command("patterns")
173
+ def patterns(
174
+ input_dir: Path = typer.Option(
175
+ Path("artefatos/historico"),
176
+ "--input-dir",
177
+ "-i",
178
+ help="Diretório contendo os JSONs de histórico para analisar padrões.",
179
+ exists=True,
180
+ file_okay=False,
181
+ dir_okay=True,
182
+ resolve_path=True
183
+ ),
184
+ output_dir: Path = typer.Option(
185
+ Path("artefatos/patterns_output"),
186
+ "--output-dir",
187
+ "-o",
188
+ help="Diretório para salvar gráficos e relatórios.",
189
+ file_okay=False,
190
+ dir_okay=True,
191
+ resolve_path=True
192
+ )
193
+ ):
194
+ try:
195
+ output_dir.mkdir(parents=True, exist_ok=True)
196
+
197
+ typer.echo(f"[*] Processando arquivos em: {input_dir}")
198
+ typer.echo(f"[*] Salvando resultados em: {output_dir}")
199
+
200
+ processar_historico_da_pasta(str(input_dir), str(output_dir))
201
+
202
+ typer.echo("\n✅ Processamento concluído.")
203
+
204
+ except Exception as erro:
205
+ typer.echo(f"\n❌ Erro ao executar patterns: {erro}")
206
+
207
+ @browser_app.command("words")
208
+ def words(
209
+ chrome: bool = typer.Option(True, "--chrome", help="Extrair palavras mais pesquisadas do Chrome"),
210
+ output_dir: Path = typer.Option(
211
+ Path("artefatos/words_output"),
212
+ "--output-dir",
213
+ "-o",
214
+ help="Diretório para salvar o JSON com palavras mais pesquisadas",
215
+ resolve_path=True
216
+ )
217
+ ):
218
+ output_dir.mkdir(parents=True, exist_ok=True)
219
+
220
+ extract_words(chrome=chrome, output_dir=output_dir)
@@ -0,0 +1,54 @@
1
+ import typer
2
+ from rich import print
3
+
4
+ describe_app = typer.Typer()
5
+
6
+ @describe_app.command("func")
7
+ def describe(
8
+ func: str = typer.Argument(..., help="Digite o nome da função que você deseja entender")
9
+ ):
10
+ docs = {
11
+ "map": {
12
+ "description": "Retorna informações detalhadas de cada host da rede.",
13
+ "example_return": [{
14
+ "host": "192.168.1.1",
15
+ "hostname": "router",
16
+ "mac": "AA:BB:CC:DD:EE:FF",
17
+ "vendor": "Cisco",
18
+ "open_ports": [22, 80],
19
+ "os_info": "Linux"
20
+ }]
21
+ },
22
+ "sweep": {
23
+ "description": "Faz um ping sweep na rede e retorna os hosts ativos.",
24
+ "example_return": ["192.168.1.1", "192.168.1.3"]
25
+ },
26
+ "scan": {
27
+ "description": "Verifica quais portas estão abertas, fechadas e filtradas.",
28
+ "example_return": [{
29
+ "port": 53,
30
+ "protocol": "UDP",
31
+ "status": "open|filtered",
32
+ "banner": "sem resposta",
33
+ "alert": "DNS"
34
+ }]
35
+ },
36
+ "fingerprinting": {
37
+ "description": "Detecta o sistema operacional, tipo de host, serviços e possíveis alertas de vulnerabilidade de um host.",
38
+ "example_return": {
39
+ "os": "Windows",
40
+ "host_type": "Servidor",
41
+ "services": ["SMB", "Web HTTP", "MySQL"],
42
+ "alerts": ["SMB vulnerável (Samba 3.0)"]
43
+ }
44
+ }
45
+ }
46
+
47
+ if func in docs:
48
+ info = docs[func]
49
+ print(f"[bold blue]Nome da Função:[/bold blue] {func}")
50
+ print(f"[bold blue]Descrição:[/bold blue] {info['description']}")
51
+ print("[bold blue]Exemplo de Retorno:[/bold blue]")
52
+ print(info['example_return'])
53
+ else:
54
+ typer.echo(f"Função {func} não encontrada.")
cli/commands/email.py ADDED
@@ -0,0 +1,3 @@
1
+ import typer
2
+
3
+ email_app = typer.Typer()
@@ -0,0 +1,126 @@
1
+ import typer
2
+ from rich import print
3
+ from rich.text import Text
4
+
5
+ from core.network.network_map import run as run_network_map
6
+ from core.network.port_scanner import scan_host
7
+ from core.network.ping_sweep import parse_network, ping_host
8
+ from core.network.fingerprinting import detect_os
9
+ from core.network.traceroute import traceroute_host
10
+ from core.network.arp_scan import arp_scan
11
+ from core.network.dns_recon import dns_recon
12
+ from core.network.ip_info import ip_info_lookup
13
+ from core.network.smb_scan import smb_scan
14
+ from core.network.snmp_scan import snmp_scan
15
+
16
+ network_app = typer.Typer(help="Conjunto de ferramentas para análise e exploração de redes")
17
+
18
+ @network_app.command("map", help="Mapeia dispositivos ativos na rede e salva os resultados em JSON/CSV")
19
+ def map(
20
+ network: str = typer.Option(..., help="Range de IPs da rede. Exemplo: 192.168.1.1-254"),
21
+ output_dir: str = typer.Option("../../output", help="Diretório para salvar os resultados"),
22
+ ):
23
+ result = run_network_map(network, output_dir)
24
+ typer.echo(f"Resultados salvos em: {result['json_file']} e {result['csv_file']}")
25
+
26
+ @network_app.command("scan", help="Realiza um scan de portas em um host específico")
27
+ def scan(
28
+ ip: str = typer.Option(..., help="Endereço IP do host. Exemplo: 192.168.0.10")
29
+ ):
30
+ result = scan_host(ip)
31
+ print(result)
32
+
33
+ @network_app.command("sweep", help="Verifica hosts ativos em um range de IPs via ping")
34
+ def sweep(
35
+ network: str = typer.Option(..., help="Range de IPs da rede. Exemplo: 192.168.1.1-254"),
36
+ ):
37
+ ips = parse_network(network)
38
+ alive_hosts = []
39
+
40
+ with typer.progressbar(ips, label="Scanning network") as progress:
41
+ for ip in progress:
42
+ if ping_host(ip):
43
+ alive_hosts.append(ip)
44
+
45
+ typer.echo(f"Hosts ativos: {alive_hosts}")
46
+
47
+ @network_app.command("fingerprinting", help="Detecta SO, serviços e portas abertas em um host")
48
+ def fingerprinting(
49
+ ip: str = typer.Option(..., help="Endereço IP do host. Exemplo: 192.168.0.10")
50
+ ):
51
+ typer.echo(f"[+] Verificando se {ip} está ativo...")
52
+ if not ping_host(ip):
53
+ typer.echo(f"[-] Host {ip} inatingível. Ping falhou.")
54
+ return
55
+
56
+ typer.echo(f"[+] Escaneando portas de {ip}...")
57
+ ports = scan_host(ip)
58
+
59
+ typer.echo(f"[+] Detectando SO, serviços e alertas...")
60
+ result = detect_os(ip, ports=ports)
61
+
62
+ print(result)
63
+
64
+ @network_app.command("traceroute", help="Exibe o caminho (hops) até um domínio ou host")
65
+ def traceroute(
66
+ domain: str = typer.Option(..., help="Informe um domínio ou hostname. Exemplo: google.com")
67
+ ):
68
+ typer.echo(f"[+] Iniciando traceroute para {domain}...")
69
+
70
+ hops = traceroute_host(domain)
71
+
72
+ with typer.progressbar(hops, label="Traceroute") as progress:
73
+ for h in hops:
74
+ rtt = h["rtt"]
75
+ hop_text = Text(f" Hop {h['hop']}: {h.get('domain', h['ip'])} - RTT: ")
76
+
77
+ if rtt is None:
78
+ hop_text.append("inacessível", style="grey50")
79
+ elif rtt < 10:
80
+ hop_text.append(f"{rtt} ms", style="green")
81
+ elif rtt < 50:
82
+ hop_text.append(f"{rtt} ms", style="yellow")
83
+ else:
84
+ hop_text.append(f"{rtt} ms", style="red")
85
+
86
+ print(hop_text)
87
+ progress.update(1)
88
+
89
+ @network_app.command("arpscan", help="Realiza varredura ARP para identificar dispositivos na rede local")
90
+ def arp(
91
+ network: str = typer.Option(..., help="Range de IPs da rede. Exemplo: 192.168.1.1-254"),
92
+ ):
93
+ result = arp_scan(network)
94
+ print(result)
95
+
96
+ @network_app.command("dnscan", help="Realiza reconhecimento DNS em um domínio ou IP")
97
+ def dns(
98
+ target: str = typer.Option(..., help="Informe o domínio ou IP alvo. Exemplo: exemplo.com ou 8.8.8.8"),
99
+ output_dir: str = typer.Option(None, help="Diretório para salvar os resultados (JSON e CSV)"),
100
+ with_subdomains: bool = typer.Option(False, help="Tentar descobrir subdomínios comuns do domínio informado"),
101
+ ):
102
+ result = dns_recon([target], output_dir=output_dir, with_subdomains=with_subdomains)
103
+
104
+ typer.echo("[+] Resultados encontrados:")
105
+ print(result)
106
+
107
+ @network_app.command("ipinfo", help="Obtém informações detalhadas sobre um IP ou hostname")
108
+ def ip_info(
109
+ ip: str = typer.Option(..., help="IP ou hostname do destino. Exemplo: 8.8.8.8"),
110
+ ):
111
+ result = ip_info_lookup(ip)
112
+ print(result)
113
+
114
+ @network_app.command("smbscan", help="Verifica serviços SMB ativos em um host")
115
+ def smb(
116
+ ip: str = typer.Option(..., help="IP ou hostname do destino. Exemplo: 192.168.0.10"),
117
+ ):
118
+ result = smb_scan([ip])
119
+ print(result)
120
+
121
+ @network_app.command("snmpscan", help="Executa varredura SNMP para identificar informações de dispositivos")
122
+ def snmp(
123
+ ip: str = typer.Option(..., help="IP ou hostname do destino. Exemplo: 192.168.0.10"),
124
+ ):
125
+ result = snmp_scan(ip)
126
+ print(result)
cli/commands/utils.py ADDED
@@ -0,0 +1,3 @@
1
+ import typer
2
+
3
+ utils_app = typer.Typer()
cli/main.py ADDED
@@ -0,0 +1,18 @@
1
+ import typer
2
+
3
+ from cli.commands.network import network_app
4
+ from cli.commands.browser import browser_app
5
+ from cli.commands.email import email_app
6
+ from cli.commands.utils import utils_app
7
+ from cli.commands.describe import describe_app
8
+
9
+ app = typer.Typer()
10
+
11
+ app.add_typer(network_app, name="network", help="Ferramentas para análise e operações em redes")
12
+ app.add_typer(browser_app, name="browser", help="Ferramentas para coleta e análise de dados de navegadores")
13
+ app.add_typer(email_app, name="email", help="Ferramentas para análise e manipulação de dados de e-mail")
14
+ app.add_typer(utils_app, name="utils", help="Funções utilitárias de apoio ao sistema")
15
+ app.add_typer(describe_app, name="describe", help="Explicações detalhadas sobre as funcionalidades disponíveis")
16
+
17
+ if __name__ == "__main__":
18
+ app()
core/__init__.py ADDED
File without changes
@@ -0,0 +1,156 @@
1
+ import os
2
+ import sqlite3
3
+ import time
4
+ import json
5
+ import shutil
6
+ from pathlib import Path
7
+
8
+ def timestamp_chrome(microsegundos):
9
+ return time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(microsegundos / 1000000 - 11644473600))
10
+
11
+ def timestamp_firefox(microsegundos):
12
+ return time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(microsegundos / 1000000))
13
+
14
+ def save_as_json(dados, navegador, usuario):
15
+ os.makedirs("artefatos/historico", exist_ok=True)
16
+ nome_arquivo = f"artefatos/historico/Historico_{navegador}_{usuario}.json"
17
+ with open(nome_arquivo, "w", encoding="utf-8") as f:
18
+ json.dump(dados, f, indent=4, ensure_ascii=False)
19
+ print(f"[OK] Histórico salvo: {nome_arquivo}")
20
+
21
+ def copy_database(origem):
22
+ destino = f"temp_{os.path.basename(origem)}"
23
+ shutil.copy2(origem, destino)
24
+ return destino
25
+
26
+ def extract_google_edge_history(caminho_banco, navegador, usuario):
27
+ caminho_temp = copy_database(caminho_banco)
28
+ try:
29
+ conn = sqlite3.connect(caminho_temp)
30
+ cursor = conn.cursor()
31
+
32
+ cursor.execute("""
33
+ SELECT url, title, visit_count, last_visit_time
34
+ FROM urls
35
+ ORDER BY last_visit_time DESC;
36
+ """)
37
+ rows = cursor.fetchall()
38
+
39
+ historico_completo = []
40
+ for url, titulo, visitas, ultimo_acesso in rows:
41
+ historico_completo.append({
42
+ "url": url,
43
+ "titulo": titulo,
44
+ "visitas": visitas,
45
+ "ultimo_acesso": timestamp_chrome(ultimo_acesso) if ultimo_acesso else "N/A"
46
+ })
47
+
48
+ ultimas_10 = historico_completo[:10]
49
+
50
+ cursor.execute("""
51
+ SELECT url, SUM(visit_count) as total_visitas
52
+ FROM urls
53
+ GROUP BY url
54
+ ORDER BY total_visitas DESC
55
+ LIMIT 5;
56
+ """)
57
+ top_5 = [{"url": u, "total_visitas": v} for u, v in cursor.fetchall()]
58
+
59
+ dados = {
60
+ "usuario": usuario,
61
+ "navegador": navegador,
62
+ "ultimas_10_urls": ultimas_10,
63
+ "top_5_sites": top_5,
64
+ "historico_completo": historico_completo
65
+ }
66
+
67
+ save_as_json(dados, navegador, usuario)
68
+
69
+ except Exception as e:
70
+ print(f"[!] Erro ({navegador}): {e}")
71
+ finally:
72
+ conn.close()
73
+ os.remove(caminho_temp)
74
+
75
+ def extract_firefox_history(caminho_banco, usuario):
76
+ caminho_temp = copy_database(caminho_banco)
77
+ try:
78
+ conn = sqlite3.connect(caminho_temp)
79
+ cursor = conn.cursor()
80
+
81
+ cursor.execute("""
82
+ SELECT url, title, visit_count, last_visit_date
83
+ FROM moz_places
84
+ ORDER BY last_visit_date DESC;
85
+ """)
86
+ rows = cursor.fetchall()
87
+
88
+ historico_completo = []
89
+ for url, titulo, visitas, ultimo_acesso in rows:
90
+ historico_completo.append({
91
+ "url": url,
92
+ "titulo": titulo,
93
+ "visitas": visitas,
94
+ "ultimo_acesso": timestamp_firefox(ultimo_acesso) if ultimo_acesso else "N/A"
95
+ })
96
+
97
+ ultimas_10 = historico_completo[:10]
98
+
99
+ cursor.execute("""
100
+ SELECT url, SUM(visit_count) as total_visitas
101
+ FROM moz_places
102
+ GROUP BY url
103
+ ORDER BY total_visitas DESC
104
+ LIMIT 5;
105
+ """)
106
+ top_5 = [{"url": u, "total_visitas": v} for u, v in cursor.fetchall()]
107
+
108
+ dados = {
109
+ "usuario": usuario,
110
+ "navegador": "Firefox",
111
+ "ultimas_10_urls": ultimas_10,
112
+ "top_5_sites": top_5,
113
+ "historico_completo": historico_completo
114
+ }
115
+
116
+ save_as_json(dados, "Firefox", usuario)
117
+
118
+ except Exception as e:
119
+ print(f"[!] Erro (Firefox): {e}")
120
+ finally:
121
+ conn.close()
122
+ os.remove(caminho_temp)
123
+
124
+ def locale_database():
125
+ usuario = os.getlogin()
126
+ home = str(Path.home())
127
+
128
+ caminho_chrome = os.path.join(home, "AppData", "Local", "Google", "Chrome", "User Data", "Default", "History")
129
+ if os.path.exists(caminho_chrome):
130
+ print("[*] Extraindo histórico do Chrome...")
131
+ extract_google_edge_history(caminho_chrome, "Chrome", usuario)
132
+ else:
133
+ print("[!] Chrome não encontrado.")
134
+
135
+ caminho_edge = os.path.join(home, "AppData", "Local", "Microsoft", "Edge", "User Data", "Default", "History")
136
+ if os.path.exists(caminho_edge):
137
+ print("[*] Extraindo histórico do Edge...")
138
+ extract_google_edge_history(caminho_edge, "Edge", usuario)
139
+ else:
140
+ print("[!] Edge não encontrado.")
141
+
142
+ caminho_firefox_perfis = os.path.join(home, "AppData", "Roaming", "Mozilla", "Firefox", "Profiles")
143
+ if os.path.exists(caminho_firefox_perfis):
144
+ for perfil in os.listdir(caminho_firefox_perfis):
145
+ caminho_sqlite = os.path.join(caminho_firefox_perfis, perfil, "places.sqlite")
146
+ if os.path.exists(caminho_sqlite):
147
+ print(f"[*] Extraindo histórico do Firefox ({perfil})...")
148
+ extract_firefox_history(caminho_sqlite, usuario)
149
+ break
150
+ else:
151
+ print("[!] places.sqlite do Firefox não encontrado.")
152
+ else:
153
+ print("[!] Perfis do Firefox não encontrados.")
154
+
155
+ if __name__ == "__main__":
156
+ locale_database()
@@ -0,0 +1,60 @@
1
+ # core/browser/words_extractor.py
2
+ import sqlite3
3
+ import shutil
4
+ import tempfile
5
+ import re
6
+ from collections import Counter
7
+ from urllib.parse import unquote
8
+ from pathlib import Path
9
+ import json
10
+ import typer
11
+
12
+ def extract_words(chrome: bool = True, output_dir: Path = Path("artefatos/words_output")):
13
+ termos_pesquisa = []
14
+
15
+ if chrome:
16
+ chrome_history_path = Path.home() / "AppData/Local/Google/Chrome/User Data/Default/History"
17
+ if not chrome_history_path.exists():
18
+ typer.echo("[!] Histórico do Chrome não encontrado.")
19
+ return
20
+
21
+ tmp_file = tempfile.NamedTemporaryFile(delete=False)
22
+ shutil.copy2(chrome_history_path, tmp_file.name)
23
+
24
+ conn = sqlite3.connect(tmp_file.name)
25
+ cursor = conn.cursor()
26
+
27
+ cursor.execute("SELECT name FROM sqlite_master WHERE type='table'")
28
+ tabelas = [t[0] for t in cursor.fetchall()]
29
+
30
+ if 'keyword_search_terms' in tabelas:
31
+ cursor.execute("SELECT term FROM keyword_search_terms")
32
+ termos = cursor.fetchall()
33
+ termos_pesquisa = [t[0] for t in termos]
34
+ else:
35
+ typer.echo("Tabela 'keyword_search_terms' não encontrada. Usando fallback por URL...")
36
+ cursor.execute("SELECT url FROM urls")
37
+ urls = cursor.fetchall()
38
+ for (url,) in urls:
39
+ match = re.search(r"[?&]q=([^&]+)", url)
40
+ if match:
41
+ termo = unquote(match.group(1))
42
+ termos_pesquisa.append(termo)
43
+
44
+ conn.close()
45
+ tmp_file.close()
46
+
47
+ todas_palavras = []
48
+ for termo in termos_pesquisa:
49
+ palavras = re.findall(r'\b\w+\b', termo.lower())
50
+ todas_palavras.extend(palavras)
51
+
52
+ contagem = Counter(todas_palavras)
53
+ palavras_mais_comuns = contagem.most_common(50)
54
+
55
+ output_dir.mkdir(parents=True, exist_ok=True)
56
+ output_file = output_dir / "most_searched_words.json"
57
+ with open(output_file, "w", encoding="utf-8") as f:
58
+ json.dump(palavras_mais_comuns, f, indent=2, ensure_ascii=False)
59
+
60
+ typer.echo(f"✅ Palavras mais pesquisadas salvas em: {output_file}")