mint-osint 1.0.2__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.
mint.py
ADDED
|
@@ -0,0 +1,692 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import time
|
|
4
|
+
import subprocess
|
|
5
|
+
import msvcrt
|
|
6
|
+
import json
|
|
7
|
+
import shutil
|
|
8
|
+
import urllib.request
|
|
9
|
+
import zipfile
|
|
10
|
+
import io
|
|
11
|
+
|
|
12
|
+
# Force UTF-8 encoding on Windows to prevent UnicodeEncodeErrors with box-drawing characters
|
|
13
|
+
if sys.platform.startswith('win'):
|
|
14
|
+
import io
|
|
15
|
+
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
|
|
16
|
+
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')
|
|
17
|
+
|
|
18
|
+
from colorama import init, Fore, Back, Style
|
|
19
|
+
|
|
20
|
+
# Initialize colorama
|
|
21
|
+
init(autoreset=True)
|
|
22
|
+
|
|
23
|
+
OPTIONS = [
|
|
24
|
+
{"name": "Sherlock (Username Scanner)", "desc": "Hunts down social media accounts by username across 300+ sites"},
|
|
25
|
+
{"name": "Holehe (Email Checker)", "desc": "Checks if an email address is registered on 120+ different websites"},
|
|
26
|
+
{"name": "SpiderFoot (OSINT Web Server)", "desc": "Automates intelligence gathering via a local web interface"},
|
|
27
|
+
{"name": "Toutatis (Instagram Extractor)", "desc": "Extracts associated emails and phone numbers from Instagram profiles"},
|
|
28
|
+
{"name": "MINT Social Tool (Social Downloader)", "desc": "Downloads photos, videos, stories, and highlights from profiles"},
|
|
29
|
+
{"name": "Update Tools (GitHub Pull)", "desc": "Pull the latest updates for all 4 external OSINT tools from their official repositories"},
|
|
30
|
+
{"name": "Exit", "desc": "Close the MINT Command Center"}
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
# Constants and Paths for MINT Social Tool (dynamically resolved from config.json if available)
|
|
34
|
+
config_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "config.json")
|
|
35
|
+
|
|
36
|
+
# Dynamic defaults based on user's home directory (standard writeable location across Windows users)
|
|
37
|
+
user_home = os.path.expanduser("~")
|
|
38
|
+
BASE = os.path.join(user_home, "mint-social")
|
|
39
|
+
COOKIES_DIR = os.path.join(BASE, "cookies")
|
|
40
|
+
|
|
41
|
+
if os.path.exists(config_path):
|
|
42
|
+
try:
|
|
43
|
+
with open(config_path, "r", encoding="utf-8") as f:
|
|
44
|
+
config_data = json.load(f)
|
|
45
|
+
if "social_dir" in config_data:
|
|
46
|
+
BASE = config_data["social_dir"]
|
|
47
|
+
COOKIES_DIR = os.path.join(BASE, "cookies")
|
|
48
|
+
except:
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
BROWSER = "chrome"
|
|
52
|
+
PYTHON = sys.executable
|
|
53
|
+
UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
|
|
54
|
+
|
|
55
|
+
PHOTO_FILTER = "extension in ('jpg','jpeg','png','gif','webp','bmp','jfif','heic','avif','tiff','svg')"
|
|
56
|
+
VIDEO_FILTER = "extension in ('mp4','webm','mkv','mov','avi','m4v','flv','wmv','3gp','mpeg','mpg','ts','f4v','mts','m2ts')"
|
|
57
|
+
MEDIA_EXTS = ["jpg", "jpeg", "png", "gif", "webp", "bmp", "jfif", "heic", "avif", "tiff", "svg", "mp4", "webm", "mkv", "mov", "avi", "m4v", "flv", "wmv", "3gp", "mpeg", "mpg", "ts", "f4v", "mts", "m2ts"]
|
|
58
|
+
|
|
59
|
+
def clear_screen():
|
|
60
|
+
os.system('cls' if os.name == 'nt' else 'clear')
|
|
61
|
+
|
|
62
|
+
def get_terminal_width():
|
|
63
|
+
try:
|
|
64
|
+
width = os.get_terminal_size().columns
|
|
65
|
+
return width if width > 20 else 80
|
|
66
|
+
except:
|
|
67
|
+
return 80
|
|
68
|
+
|
|
69
|
+
def print_centered(text, visible_len, color=""):
|
|
70
|
+
width = get_terminal_width()
|
|
71
|
+
padding = max(0, (width - visible_len) // 2)
|
|
72
|
+
print(" " * padding + color + text)
|
|
73
|
+
|
|
74
|
+
def wrap_text(text, max_len=52):
|
|
75
|
+
words = text.split()
|
|
76
|
+
lines = []
|
|
77
|
+
current_line = []
|
|
78
|
+
current_len = 0
|
|
79
|
+
for word in words:
|
|
80
|
+
if current_len + len(word) + len(current_line) > max_len:
|
|
81
|
+
lines.append(" ".join(current_line))
|
|
82
|
+
current_line = [word]
|
|
83
|
+
current_len = len(word)
|
|
84
|
+
else:
|
|
85
|
+
current_line.append(word)
|
|
86
|
+
current_len += len(word)
|
|
87
|
+
if current_line:
|
|
88
|
+
lines.append(" ".join(current_line))
|
|
89
|
+
return lines
|
|
90
|
+
|
|
91
|
+
def draw_header(subtitle="Select a tool to launch from the menu below:"):
|
|
92
|
+
logo_lines = [
|
|
93
|
+
" ▄ ",
|
|
94
|
+
" ▄█▀█▄ ",
|
|
95
|
+
" ▄██ ██▄ ",
|
|
96
|
+
" ████ ████ ",
|
|
97
|
+
" ▄████ ████▄ ",
|
|
98
|
+
" ██████ ██████ ",
|
|
99
|
+
" ▀████ ████▀ ",
|
|
100
|
+
" ▄█████ █████▄ ",
|
|
101
|
+
" ▀████ ████▀ ",
|
|
102
|
+
" ▀██ ██▀ ",
|
|
103
|
+
" █ █ ",
|
|
104
|
+
" ▀ ▀ "
|
|
105
|
+
]
|
|
106
|
+
|
|
107
|
+
current_workspace = os.getcwd()
|
|
108
|
+
|
|
109
|
+
for line in logo_lines:
|
|
110
|
+
print_centered(line, 26, Fore.GREEN)
|
|
111
|
+
|
|
112
|
+
print()
|
|
113
|
+
print_centered("M I N T v1.0", 14, Fore.GREEN + Style.BRIGHT)
|
|
114
|
+
print_centered("─" * 50, 50, Fore.LIGHTBLACK_EX)
|
|
115
|
+
print_centered("The Unified OSINT & Media Command Center", 40, Fore.WHITE + Style.BRIGHT)
|
|
116
|
+
print_centered(f"Workspace: {current_workspace}", len(f"Workspace: {current_workspace}"), Fore.LIGHTBLACK_EX)
|
|
117
|
+
print_centered(Fore.LIGHTBLACK_EX + "GitHub: " + Fore.BLUE + "https://github.com/sayfalse", 36)
|
|
118
|
+
print_centered("Environment: Windows • Python: 3.14", 37, Fore.LIGHTBLACK_EX)
|
|
119
|
+
print()
|
|
120
|
+
print_centered(subtitle, len(subtitle), Fore.WHITE)
|
|
121
|
+
print()
|
|
122
|
+
|
|
123
|
+
def draw_menu(selected_index):
|
|
124
|
+
clear_screen()
|
|
125
|
+
draw_header()
|
|
126
|
+
|
|
127
|
+
menu_width = 43
|
|
128
|
+
width = get_terminal_width()
|
|
129
|
+
menu_padding = max(0, (width - menu_width) // 2)
|
|
130
|
+
|
|
131
|
+
for i, opt in enumerate(OPTIONS):
|
|
132
|
+
if i == selected_index:
|
|
133
|
+
print(" " * menu_padding + Fore.GREEN + Style.BRIGHT + " ❯ " + Back.GREEN + Fore.BLACK + f" {opt['name'].ljust(38)} " + Style.RESET_ALL)
|
|
134
|
+
else:
|
|
135
|
+
print(" " * menu_padding + Fore.WHITE + f" {opt['name']}")
|
|
136
|
+
print()
|
|
137
|
+
|
|
138
|
+
desc_width = 50
|
|
139
|
+
desc_padding = max(0, (width - desc_width) // 2)
|
|
140
|
+
|
|
141
|
+
print(" " * desc_padding + Fore.LIGHTBLACK_EX + "─" * 50)
|
|
142
|
+
current_desc = OPTIONS[selected_index]['desc']
|
|
143
|
+
wrapped_desc = wrap_text(current_desc, 44)
|
|
144
|
+
for idx, d_line in enumerate(wrapped_desc):
|
|
145
|
+
if idx == 0:
|
|
146
|
+
print(" " * desc_padding + Fore.YELLOW + "Info: " + Fore.WHITE + d_line)
|
|
147
|
+
else:
|
|
148
|
+
print(" " * desc_padding + " " + Fore.WHITE + d_line)
|
|
149
|
+
print(" " * desc_padding + Fore.LIGHTBLACK_EX + "─" * 50)
|
|
150
|
+
print()
|
|
151
|
+
|
|
152
|
+
print_centered(Fore.BLACK + Back.LIGHTBLACK_EX + " ↑/↓: Move • Enter: Run • 1-7: Hotkey • Esc: Exit ", 58)
|
|
153
|
+
print()
|
|
154
|
+
|
|
155
|
+
def get_key():
|
|
156
|
+
ch = msvcrt.getch()
|
|
157
|
+
if ch in (b'\x00', b'\xe0'):
|
|
158
|
+
ch = msvcrt.getch()
|
|
159
|
+
if ch == b'H': return 'up'
|
|
160
|
+
if ch == b'P': return 'down'
|
|
161
|
+
elif ch == b'\r':
|
|
162
|
+
return 'enter'
|
|
163
|
+
elif ch == b'\x1b':
|
|
164
|
+
return 'esc'
|
|
165
|
+
else:
|
|
166
|
+
try:
|
|
167
|
+
return ch.decode('utf-8')
|
|
168
|
+
except:
|
|
169
|
+
return None
|
|
170
|
+
|
|
171
|
+
def prompt_input(label):
|
|
172
|
+
print(Fore.GREEN + f" ❯ {label}: " + Fore.WHITE, end="")
|
|
173
|
+
sys.stdout.flush()
|
|
174
|
+
return input().strip()
|
|
175
|
+
|
|
176
|
+
def run_command(cmd_string):
|
|
177
|
+
try:
|
|
178
|
+
subprocess.run(cmd_string, shell=True)
|
|
179
|
+
except KeyboardInterrupt:
|
|
180
|
+
print(Fore.YELLOW + "\n [!] Process stopped by user.")
|
|
181
|
+
except Exception as e:
|
|
182
|
+
print(Fore.RED + f"\n [!] Error executing command: {e}")
|
|
183
|
+
|
|
184
|
+
def parse_profile_url(url, platform):
|
|
185
|
+
url = url.strip()
|
|
186
|
+
if not url:
|
|
187
|
+
return None
|
|
188
|
+
if not url.lower().startswith("http"):
|
|
189
|
+
url = "https://" + url
|
|
190
|
+
t = url.replace("http://", "").replace("https://", "")
|
|
191
|
+
if t.startswith("/"):
|
|
192
|
+
t = t[1:]
|
|
193
|
+
parts = t.split("/")
|
|
194
|
+
if len(parts) < 2:
|
|
195
|
+
return None
|
|
196
|
+
dom = parts[0].replace("www.", "").lower()
|
|
197
|
+
usr = parts[1]
|
|
198
|
+
|
|
199
|
+
if platform == "instagram" and dom != "instagram.com": return None
|
|
200
|
+
if platform == "tiktok" and dom != "tiktok.com": return None
|
|
201
|
+
if platform == "facebook" and dom != "facebook.com": return None
|
|
202
|
+
if platform == "x" and dom not in ["x.com", "twitter.com"]: return None
|
|
203
|
+
|
|
204
|
+
username = usr.replace("@", "")
|
|
205
|
+
for char in ["?", "#", "/"]:
|
|
206
|
+
username = username.split(char)[0]
|
|
207
|
+
return username if username else None
|
|
208
|
+
|
|
209
|
+
def get_cookies_arg(platform):
|
|
210
|
+
if platform == "tiktok":
|
|
211
|
+
return f"--cookies-from-browser {BROWSER}"
|
|
212
|
+
|
|
213
|
+
possible_dirs = [d for d in [COOKIES_DIR, BASE] if d and os.path.exists(d)]
|
|
214
|
+
possible_names = [
|
|
215
|
+
f"{platform}.com_cookies.txt",
|
|
216
|
+
f"{platform}_cookies.txt",
|
|
217
|
+
f"{platform}.com_cookies.txt"
|
|
218
|
+
]
|
|
219
|
+
|
|
220
|
+
possible_paths = [os.path.join(d, name) for d in possible_dirs for name in possible_names]
|
|
221
|
+
for path in possible_paths:
|
|
222
|
+
if os.path.exists(path):
|
|
223
|
+
return f'--cookies "{path}"'
|
|
224
|
+
|
|
225
|
+
return ""
|
|
226
|
+
|
|
227
|
+
def check_archive(directory, archive_path):
|
|
228
|
+
if not os.path.exists(directory):
|
|
229
|
+
if os.path.exists(archive_path):
|
|
230
|
+
try: os.remove(archive_path)
|
|
231
|
+
except: pass
|
|
232
|
+
return
|
|
233
|
+
|
|
234
|
+
media_count = 0
|
|
235
|
+
for root, _, files in os.walk(directory):
|
|
236
|
+
for file in files:
|
|
237
|
+
ext = file.split(".")[-1].lower()
|
|
238
|
+
if ext in MEDIA_EXTS:
|
|
239
|
+
media_count += 1
|
|
240
|
+
|
|
241
|
+
archive_lines = 0
|
|
242
|
+
if os.path.exists(archive_path):
|
|
243
|
+
try:
|
|
244
|
+
with open(archive_path, "r", encoding="utf-8", errors="ignore") as f:
|
|
245
|
+
archive_lines = len(f.readlines())
|
|
246
|
+
except:
|
|
247
|
+
pass
|
|
248
|
+
|
|
249
|
+
if media_count == 0:
|
|
250
|
+
if os.path.exists(archive_path):
|
|
251
|
+
try:
|
|
252
|
+
os.remove(archive_path)
|
|
253
|
+
print(" Empty, archive cleared.")
|
|
254
|
+
except:
|
|
255
|
+
pass
|
|
256
|
+
elif archive_lines == 0:
|
|
257
|
+
print(f" {media_count} files, rebuilding...")
|
|
258
|
+
else:
|
|
259
|
+
print(f" {media_count} files, archive ok.")
|
|
260
|
+
|
|
261
|
+
def run_gallery_dl(gdir, gfil, gck, gurl):
|
|
262
|
+
os.makedirs(gdir, exist_ok=True)
|
|
263
|
+
archive_path = os.path.join(gdir, "archive.txt")
|
|
264
|
+
cmd = f'"{PYTHON}" -m gallery_dl -D "{gdir}" --filter "{gfil}" {gck} -o "user-agent={UA}" --download-archive "{archive_path}" --sleep-request 5 "{gurl}"'
|
|
265
|
+
return subprocess.run(cmd, shell=True).returncode
|
|
266
|
+
|
|
267
|
+
def run_yt_dlp(ydir, yck, yurl):
|
|
268
|
+
os.makedirs(ydir, exist_ok=True)
|
|
269
|
+
cmd = f'"{PYTHON}" -m yt_dlp -o "{ydir}\\%(title)s.%(ext)s" {yck} --no-playlist --user-agent "{UA}" "{yurl}"'
|
|
270
|
+
return subprocess.run(cmd, shell=True).returncode
|
|
271
|
+
|
|
272
|
+
def download_photos(dest_dir, cookies_arg, url):
|
|
273
|
+
check_archive(os.path.join(dest_dir, "Photos"), os.path.join(dest_dir, "Photos", "archive.txt"))
|
|
274
|
+
print(" [Photos]")
|
|
275
|
+
ret = run_gallery_dl(os.path.join(dest_dir, "Photos"), PHOTO_FILTER, cookies_arg, url)
|
|
276
|
+
if ret != 0:
|
|
277
|
+
print(" [ERROR]")
|
|
278
|
+
|
|
279
|
+
def download_videos(dest_dir, cookies_arg, url):
|
|
280
|
+
check_archive(os.path.join(dest_dir, "Videos"), os.path.join(dest_dir, "Videos", "archive.txt"))
|
|
281
|
+
print(" [Videos]")
|
|
282
|
+
ret = run_gallery_dl(os.path.join(dest_dir, "Videos"), VIDEO_FILTER, cookies_arg, url)
|
|
283
|
+
if ret != 0:
|
|
284
|
+
print(" Trying yt-dlp...")
|
|
285
|
+
ret_yt = run_yt_dlp(os.path.join(dest_dir, "Videos"), cookies_arg, url)
|
|
286
|
+
if ret_yt != 0:
|
|
287
|
+
print(" [ERROR]")
|
|
288
|
+
|
|
289
|
+
def download_stories(dest_dir, cookies_arg, platform, username):
|
|
290
|
+
if platform != "instagram":
|
|
291
|
+
return
|
|
292
|
+
check_archive(os.path.join(dest_dir, "Stories"), os.path.join(dest_dir, "Stories", "archive.txt"))
|
|
293
|
+
print(" [Stories]")
|
|
294
|
+
url = f"https://www.instagram.com/stories/{username}/"
|
|
295
|
+
ret = run_gallery_dl(os.path.join(dest_dir, "Stories"), "true", cookies_arg, url)
|
|
296
|
+
if ret != 0:
|
|
297
|
+
print(" [ERROR]")
|
|
298
|
+
|
|
299
|
+
def download_highlights(dest_dir, cookies_arg, platform, username):
|
|
300
|
+
if platform != "instagram":
|
|
301
|
+
return
|
|
302
|
+
check_archive(os.path.join(dest_dir, "Highlights"), os.path.join(dest_dir, "Highlights", "archive.txt"))
|
|
303
|
+
print(" [Highlights]")
|
|
304
|
+
url = f"https://www.instagram.com/{username}/highlights/"
|
|
305
|
+
ret = run_gallery_dl(os.path.join(dest_dir, "Highlights"), "true", cookies_arg, url)
|
|
306
|
+
if ret != 0:
|
|
307
|
+
print(" [ERROR]")
|
|
308
|
+
|
|
309
|
+
def download_profile(username, dest_dir, platform, original_url, media_choice):
|
|
310
|
+
cookies_arg = get_cookies_arg(platform)
|
|
311
|
+
url = original_url.strip()
|
|
312
|
+
while url.endswith("/"):
|
|
313
|
+
url = url[:-1]
|
|
314
|
+
|
|
315
|
+
if media_choice in ["1", "5", "7"]:
|
|
316
|
+
download_photos(dest_dir, cookies_arg, url)
|
|
317
|
+
if media_choice in ["2", "5", "7"]:
|
|
318
|
+
download_videos(dest_dir, cookies_arg, url)
|
|
319
|
+
if media_choice in ["3", "6", "7"]:
|
|
320
|
+
download_stories(dest_dir, cookies_arg, platform, username)
|
|
321
|
+
if media_choice in ["4", "6", "7"]:
|
|
322
|
+
download_highlights(dest_dir, cookies_arg, platform, username)
|
|
323
|
+
|
|
324
|
+
def run_social_tool_downloads(media_choice):
|
|
325
|
+
os.makedirs(BASE, exist_ok=True)
|
|
326
|
+
os.makedirs(COOKIES_DIR, exist_ok=True)
|
|
327
|
+
|
|
328
|
+
platforms = ["instagram", "tiktok", "facebook", "x"]
|
|
329
|
+
for platform in platforms:
|
|
330
|
+
profile_file = os.path.join(BASE, f"{platform}_profiles.txt")
|
|
331
|
+
if os.path.exists(profile_file):
|
|
332
|
+
if platform == "x":
|
|
333
|
+
print(Fore.CYAN + "\n [X/TWITTER]")
|
|
334
|
+
else:
|
|
335
|
+
print(Fore.CYAN + f"\n [{platform.upper()}]")
|
|
336
|
+
|
|
337
|
+
try:
|
|
338
|
+
with open(profile_file, "r", encoding="utf-8", errors="ignore") as f:
|
|
339
|
+
lines = f.readlines()
|
|
340
|
+
except Exception as e:
|
|
341
|
+
print(Fore.RED + f" [!] Error reading profiles: {e}")
|
|
342
|
+
continue
|
|
343
|
+
|
|
344
|
+
for line in lines:
|
|
345
|
+
line = line.strip()
|
|
346
|
+
if not line:
|
|
347
|
+
continue
|
|
348
|
+
if line.startswith("#") or line.startswith(";"):
|
|
349
|
+
continue
|
|
350
|
+
|
|
351
|
+
username = parse_profile_url(line, platform)
|
|
352
|
+
if username and username != "INVALID_URL":
|
|
353
|
+
print(Fore.WHITE + f" --- {username}")
|
|
354
|
+
dest_dir = os.path.join(BASE, platform, username)
|
|
355
|
+
download_profile(username, dest_dir, platform, line, media_choice)
|
|
356
|
+
|
|
357
|
+
def draw_social_menu(selected_index):
|
|
358
|
+
clear_screen()
|
|
359
|
+
draw_header("Running: MINT Social Tool (Social Downloader)")
|
|
360
|
+
|
|
361
|
+
print_centered("=== MINT Social Tool ===", 24, Fore.GREEN + Style.BRIGHT)
|
|
362
|
+
print()
|
|
363
|
+
print_centered("What do you want to download?", 29, Fore.WHITE + Style.BRIGHT)
|
|
364
|
+
print()
|
|
365
|
+
|
|
366
|
+
menu_options = [
|
|
367
|
+
"Photos only",
|
|
368
|
+
"Videos only",
|
|
369
|
+
"Stories only",
|
|
370
|
+
"Highlights only",
|
|
371
|
+
"Photos + Videos",
|
|
372
|
+
"Stories + Highlights",
|
|
373
|
+
"All",
|
|
374
|
+
"Back to Main Menu"
|
|
375
|
+
]
|
|
376
|
+
|
|
377
|
+
menu_width = 30
|
|
378
|
+
width = get_terminal_width()
|
|
379
|
+
menu_padding = max(0, (width - menu_width) // 2)
|
|
380
|
+
|
|
381
|
+
for i, opt in enumerate(menu_options):
|
|
382
|
+
if i == selected_index:
|
|
383
|
+
print(" " * menu_padding + Fore.GREEN + Style.BRIGHT + f" ❯ " + Back.GREEN + Fore.BLACK + f" [{i+1}] {opt.ljust(20)} " + Style.RESET_ALL)
|
|
384
|
+
else:
|
|
385
|
+
print(" " * menu_padding + Fore.WHITE + f" [{i+1}] {opt}")
|
|
386
|
+
|
|
387
|
+
print()
|
|
388
|
+
print_centered(" ↑/↓: Move • Enter: Select • 1-8: Hotkey ", 48, Fore.BLACK + Back.LIGHTBLACK_EX)
|
|
389
|
+
print()
|
|
390
|
+
|
|
391
|
+
def run_social_tool_tui():
|
|
392
|
+
selected_index = 0
|
|
393
|
+
while True:
|
|
394
|
+
draw_social_menu(selected_index)
|
|
395
|
+
key = get_key()
|
|
396
|
+
|
|
397
|
+
if key == 'up':
|
|
398
|
+
selected_index = (selected_index - 1) % 8
|
|
399
|
+
elif key == 'down':
|
|
400
|
+
selected_index = (selected_index + 1) % 8
|
|
401
|
+
elif key == 'esc':
|
|
402
|
+
break
|
|
403
|
+
elif key in ['1', '2', '3', '4', '5', '6', '7', '8']:
|
|
404
|
+
selected_index = int(key) - 1
|
|
405
|
+
key = 'enter'
|
|
406
|
+
|
|
407
|
+
if key == 'enter':
|
|
408
|
+
if selected_index == 7:
|
|
409
|
+
break
|
|
410
|
+
|
|
411
|
+
clear_screen()
|
|
412
|
+
media_choice = str(selected_index + 1)
|
|
413
|
+
draw_header(f"Running: MINT Social Tool - Mode [{media_choice}]")
|
|
414
|
+
|
|
415
|
+
print(Fore.GREEN + Style.BRIGHT + f" === MINT Social Tool - Mode [{media_choice}] ===")
|
|
416
|
+
print()
|
|
417
|
+
print(Fore.YELLOW + f" [+] Starting downloads in mode [{media_choice}]...")
|
|
418
|
+
print(Fore.LIGHTBLACK_EX + " " + "─" * 50)
|
|
419
|
+
|
|
420
|
+
try:
|
|
421
|
+
run_social_tool_downloads(media_choice)
|
|
422
|
+
except KeyboardInterrupt:
|
|
423
|
+
print(Fore.YELLOW + "\n [!] Downloads interrupted by user.")
|
|
424
|
+
except Exception as e:
|
|
425
|
+
print(Fore.RED + f"\n [!] Error running downloads: {e}")
|
|
426
|
+
|
|
427
|
+
print(Fore.LIGHTBLACK_EX + "\n " + "─" * 50)
|
|
428
|
+
print(Fore.GREEN + " [+] Done.")
|
|
429
|
+
print()
|
|
430
|
+
input(" Press Enter to return to MINT Social Tool menu...")
|
|
431
|
+
|
|
432
|
+
def update_single_tool(key, name, path):
|
|
433
|
+
if os.path.exists(os.path.join(path, ".git")):
|
|
434
|
+
try:
|
|
435
|
+
print(Fore.LIGHTBLACK_EX + " Running git pull...")
|
|
436
|
+
subprocess.run(["git", "-C", path, "pull"], check=True)
|
|
437
|
+
|
|
438
|
+
req_file = os.path.join(path, "requirements.txt")
|
|
439
|
+
if os.path.exists(req_file):
|
|
440
|
+
if key == "spiderfoot":
|
|
441
|
+
try:
|
|
442
|
+
with open(req_file, "r", encoding="utf-8") as f:
|
|
443
|
+
content = f.read()
|
|
444
|
+
if "lxml>=4.9.2,<5" in content:
|
|
445
|
+
content = content.replace("lxml>=4.9.2,<5", "lxml>=4.9.2")
|
|
446
|
+
with open(req_file, "w", encoding="utf-8") as f:
|
|
447
|
+
f.write(content)
|
|
448
|
+
except Exception as e:
|
|
449
|
+
print(Fore.RED + f" [!] Warning: Failed to patch SpiderFoot requirements: {e}")
|
|
450
|
+
|
|
451
|
+
print(Fore.LIGHTBLACK_EX + " Upgrading dependencies...")
|
|
452
|
+
subprocess.run([sys.executable, "-m", "pip", "install", "-r", req_file], check=True)
|
|
453
|
+
|
|
454
|
+
print(Fore.GREEN + f" [+] {name} updated successfully.")
|
|
455
|
+
except Exception as e:
|
|
456
|
+
print(Fore.RED + f" [!] Error updating {name}: {e}")
|
|
457
|
+
return
|
|
458
|
+
|
|
459
|
+
print(Fore.LIGHTBLACK_EX + " Not a git repo. Attempting re-download from GitHub...")
|
|
460
|
+
try:
|
|
461
|
+
repos = {
|
|
462
|
+
"sherlock": "sherlock-project/sherlock",
|
|
463
|
+
"holehe": "megadose/holehe",
|
|
464
|
+
"spiderfoot": "smicallef/spiderfoot",
|
|
465
|
+
"toutatis": "leogout/toutatis"
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
repo = repos.get(key)
|
|
469
|
+
if not repo:
|
|
470
|
+
return
|
|
471
|
+
|
|
472
|
+
zip_url = f"https://github.com/{repo}/archive/refs/heads/master.zip"
|
|
473
|
+
try:
|
|
474
|
+
req = urllib.request.Request(zip_url, headers={'User-Agent': 'Mozilla/5.0'})
|
|
475
|
+
with urllib.request.urlopen(req) as response:
|
|
476
|
+
zip_data = response.read()
|
|
477
|
+
except:
|
|
478
|
+
zip_url = f"https://github.com/{repo}/archive/refs/heads/main.zip"
|
|
479
|
+
req = urllib.request.Request(zip_url, headers={'User-Agent': 'Mozilla/5.0'})
|
|
480
|
+
with urllib.request.urlopen(req) as response:
|
|
481
|
+
zip_data = response.read()
|
|
482
|
+
|
|
483
|
+
temp_dir = os.path.join(os.path.dirname(path), f"{key}_temp_update")
|
|
484
|
+
if os.path.exists(temp_dir):
|
|
485
|
+
shutil.rmtree(temp_dir)
|
|
486
|
+
os.makedirs(temp_dir, exist_ok=True)
|
|
487
|
+
|
|
488
|
+
with zipfile.ZipFile(io.BytesIO(zip_data)) as zip_ref:
|
|
489
|
+
zip_ref.extractall(temp_dir)
|
|
490
|
+
|
|
491
|
+
extracted_folder = None
|
|
492
|
+
for folder in os.listdir(temp_dir):
|
|
493
|
+
if folder.startswith(key) or folder.startswith(name.lower()):
|
|
494
|
+
extracted_folder = os.path.join(temp_dir, folder)
|
|
495
|
+
break
|
|
496
|
+
|
|
497
|
+
if extracted_folder:
|
|
498
|
+
for item in os.listdir(extracted_folder):
|
|
499
|
+
s = os.path.join(extracted_folder, item)
|
|
500
|
+
d = os.path.join(path, item)
|
|
501
|
+
if os.path.isdir(s):
|
|
502
|
+
if os.path.exists(d):
|
|
503
|
+
shutil.rmtree(d)
|
|
504
|
+
shutil.copytree(s, d)
|
|
505
|
+
else:
|
|
506
|
+
shutil.copy2(s, d)
|
|
507
|
+
|
|
508
|
+
req_file = os.path.join(path, "requirements.txt")
|
|
509
|
+
if os.path.exists(req_file):
|
|
510
|
+
if key == "spiderfoot":
|
|
511
|
+
try:
|
|
512
|
+
with open(req_file, "r", encoding="utf-8") as f:
|
|
513
|
+
content = f.read()
|
|
514
|
+
if "lxml>=4.9.2,<5" in content:
|
|
515
|
+
content = content.replace("lxml>=4.9.2,<5", "lxml>=4.9.2")
|
|
516
|
+
with open(req_file, "w", encoding="utf-8") as f:
|
|
517
|
+
f.write(content)
|
|
518
|
+
except Exception as e:
|
|
519
|
+
print(Fore.RED + f" [!] Warning: Failed to patch SpiderFoot requirements: {e}")
|
|
520
|
+
subprocess.run([sys.executable, "-m", "pip", "install", "-r", req_file], check=True)
|
|
521
|
+
|
|
522
|
+
print(Fore.GREEN + f" [+] {name} re-downloaded and updated successfully.")
|
|
523
|
+
else:
|
|
524
|
+
print(Fore.RED + f" [!] Could not locate extracted files in zip.")
|
|
525
|
+
|
|
526
|
+
if os.path.exists(temp_dir):
|
|
527
|
+
shutil.rmtree(temp_dir)
|
|
528
|
+
except Exception as e:
|
|
529
|
+
print(Fore.RED + f" [!] Error downloading update: {e}")
|
|
530
|
+
|
|
531
|
+
def run_tools_update():
|
|
532
|
+
clear_screen()
|
|
533
|
+
draw_header("Updating OSINT Tools from GitHub")
|
|
534
|
+
|
|
535
|
+
print(Fore.GREEN + Style.BRIGHT + " === Update OSINT Tools ===")
|
|
536
|
+
print()
|
|
537
|
+
|
|
538
|
+
config_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "config.json")
|
|
539
|
+
if not os.path.exists(config_path):
|
|
540
|
+
print(Fore.RED + " [!] Configuration file (config.json) not found.")
|
|
541
|
+
print(Fore.YELLOW + " [+] Please run setup.py/setup.bat first to configure and install the tools.")
|
|
542
|
+
print()
|
|
543
|
+
input(" Press Enter to return to menu...")
|
|
544
|
+
return
|
|
545
|
+
|
|
546
|
+
try:
|
|
547
|
+
with open(config_path, "r", encoding="utf-8") as f:
|
|
548
|
+
config = json.load(f)
|
|
549
|
+
except Exception as e:
|
|
550
|
+
print(Fore.RED + f" [!] Error reading config.json: {e}")
|
|
551
|
+
print()
|
|
552
|
+
input(" Press Enter to return to menu...")
|
|
553
|
+
return
|
|
554
|
+
|
|
555
|
+
tools = {
|
|
556
|
+
"sherlock": ("Sherlock", config.get("sherlock_path")),
|
|
557
|
+
"holehe": ("Holehe", config.get("holehe_path")),
|
|
558
|
+
"spiderfoot": ("SpiderFoot", config.get("spiderfoot_path")),
|
|
559
|
+
"toutatis": ("Toutatis", config.get("toutatis_path"))
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
print(Fore.YELLOW + " [+] Checking for updates from official GitHub repositories...\n")
|
|
563
|
+
|
|
564
|
+
for key, (name, path) in tools.items():
|
|
565
|
+
if not path or not os.path.exists(path):
|
|
566
|
+
print(Fore.RED + f" [!] {name} path not configured or directory does not exist.")
|
|
567
|
+
continue
|
|
568
|
+
|
|
569
|
+
print(Fore.WHITE + f" ❯ Updating {name} at {path}...")
|
|
570
|
+
update_single_tool(key, name, path)
|
|
571
|
+
print()
|
|
572
|
+
|
|
573
|
+
print(Fore.GREEN + Style.BRIGHT + " [+] Update process finished.")
|
|
574
|
+
print()
|
|
575
|
+
input(" Press Enter to return to menu...")
|
|
576
|
+
|
|
577
|
+
def main():
|
|
578
|
+
if len(sys.argv) > 1:
|
|
579
|
+
if sys.argv[1] == "--social":
|
|
580
|
+
run_social_tool_tui()
|
|
581
|
+
sys.exit(0)
|
|
582
|
+
elif sys.argv[1] == "--update":
|
|
583
|
+
run_tools_update()
|
|
584
|
+
sys.exit(0)
|
|
585
|
+
|
|
586
|
+
selected_index = 0
|
|
587
|
+
|
|
588
|
+
while True:
|
|
589
|
+
draw_menu(selected_index)
|
|
590
|
+
key = get_key()
|
|
591
|
+
|
|
592
|
+
if key == 'up':
|
|
593
|
+
selected_index = (selected_index - 1) % len(OPTIONS)
|
|
594
|
+
elif key == 'down':
|
|
595
|
+
selected_index = (selected_index + 1) % len(OPTIONS)
|
|
596
|
+
elif key == 'esc':
|
|
597
|
+
break
|
|
598
|
+
elif key in ['1', '2', '3', '4', '5', '6', '7']:
|
|
599
|
+
idx = int(key) - 1
|
|
600
|
+
selected_index = idx
|
|
601
|
+
key = 'enter'
|
|
602
|
+
|
|
603
|
+
if key == 'enter':
|
|
604
|
+
clear_screen()
|
|
605
|
+
|
|
606
|
+
if selected_index == 0: # Sherlock
|
|
607
|
+
draw_header("Running: Sherlock (Username Scanner)")
|
|
608
|
+
print(Fore.GREEN + Style.BRIGHT + " === Sherlock (Username Scanner) ===")
|
|
609
|
+
print()
|
|
610
|
+
username = prompt_input("Enter target username")
|
|
611
|
+
if not username:
|
|
612
|
+
continue
|
|
613
|
+
if username.lower() == "/update":
|
|
614
|
+
run_tools_update()
|
|
615
|
+
continue
|
|
616
|
+
|
|
617
|
+
print(Fore.YELLOW + f"\n [+] Querying 300+ platforms for '{username}'...\n")
|
|
618
|
+
run_command(f"sherlock {username}")
|
|
619
|
+
print()
|
|
620
|
+
input(" Press Enter to return to menu...")
|
|
621
|
+
|
|
622
|
+
elif selected_index == 1: # Holehe
|
|
623
|
+
draw_header("Running: Holehe (Email Checker)")
|
|
624
|
+
print(Fore.GREEN + Style.BRIGHT + " === Holehe (Email Checker) ===")
|
|
625
|
+
print()
|
|
626
|
+
email = prompt_input("Enter target email address")
|
|
627
|
+
if not email:
|
|
628
|
+
continue
|
|
629
|
+
if email.lower() == "/update":
|
|
630
|
+
run_tools_update()
|
|
631
|
+
continue
|
|
632
|
+
|
|
633
|
+
print(Fore.YELLOW + f"\n [+] Querying registration endpoints for '{email}'...\n")
|
|
634
|
+
run_command(f"holehe {email}")
|
|
635
|
+
print()
|
|
636
|
+
input(" Press Enter to return to menu...")
|
|
637
|
+
|
|
638
|
+
elif selected_index == 2: # SpiderFoot
|
|
639
|
+
draw_header("Running: SpiderFoot (OSINT Web Server)")
|
|
640
|
+
print(Fore.GREEN + Style.BRIGHT + " === SpiderFoot (OSINT Web Server) ===")
|
|
641
|
+
print()
|
|
642
|
+
print(Fore.GREEN + " [+] Starting SpiderFoot local web server...")
|
|
643
|
+
print(Fore.YELLOW + " [+] Dashboard URL: http://127.0.0.1:5001")
|
|
644
|
+
print(Fore.RED + " [+] Press Ctrl+C inside this window to stop the server.")
|
|
645
|
+
print()
|
|
646
|
+
run_command("spiderfoot")
|
|
647
|
+
print()
|
|
648
|
+
input(" Press Enter to return to menu...")
|
|
649
|
+
|
|
650
|
+
elif selected_index == 3: # Toutatis
|
|
651
|
+
draw_header("Running: Toutatis (Instagram Extractor)")
|
|
652
|
+
print(Fore.GREEN + Style.BRIGHT + " === Toutatis (Instagram Extractor) ===")
|
|
653
|
+
print()
|
|
654
|
+
username = prompt_input("Enter target Instagram username")
|
|
655
|
+
if not username:
|
|
656
|
+
continue
|
|
657
|
+
if username.lower() == "/update":
|
|
658
|
+
run_tools_update()
|
|
659
|
+
continue
|
|
660
|
+
|
|
661
|
+
sessionid = prompt_input("Enter Instagram Session ID (optional - press Enter to skip)")
|
|
662
|
+
if sessionid:
|
|
663
|
+
cmd = f"toutatis -u {username} -s {sessionid}"
|
|
664
|
+
else:
|
|
665
|
+
cmd = f"toutatis -u {username}"
|
|
666
|
+
print(Fore.YELLOW + f"\n [+] Querying Instagram API for '{username}'...\n")
|
|
667
|
+
run_command(cmd)
|
|
668
|
+
print()
|
|
669
|
+
input(" Press Enter to return to menu...")
|
|
670
|
+
|
|
671
|
+
elif selected_index == 4: # MINT Social Tool (Social Downloader)
|
|
672
|
+
run_social_tool_tui()
|
|
673
|
+
|
|
674
|
+
elif selected_index == 5: # Update Tools
|
|
675
|
+
run_tools_update()
|
|
676
|
+
|
|
677
|
+
elif selected_index == 6: # Exit
|
|
678
|
+
break
|
|
679
|
+
|
|
680
|
+
clear_screen()
|
|
681
|
+
print(Fore.GREEN + Style.BRIGHT + "========================================")
|
|
682
|
+
print(Fore.GREEN + Style.BRIGHT + " Thank you for using MINT. Goodbye! ")
|
|
683
|
+
print(Fore.GREEN + Style.BRIGHT + "========================================")
|
|
684
|
+
time.sleep(1)
|
|
685
|
+
|
|
686
|
+
if __name__ == "__main__":
|
|
687
|
+
try:
|
|
688
|
+
main()
|
|
689
|
+
except KeyboardInterrupt:
|
|
690
|
+
clear_screen()
|
|
691
|
+
print(Fore.GREEN + "\nGoodbye!")
|
|
692
|
+
sys.exit(0)
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mint-osint
|
|
3
|
+
Version: 1.0.2
|
|
4
|
+
Summary: MINT — The Unified OSINT & Media Command Center
|
|
5
|
+
Author: sayfalse
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: colorama>=0.4.6
|
|
9
|
+
|
|
10
|
+
<p align="center">
|
|
11
|
+
<img src="logo.png" alt="MINT Logo" width="400">
|
|
12
|
+
</p>
|
|
13
|
+
|
|
14
|
+
<h1 align="center">MINT — The Unified OSINT & Media Command Center</h1>
|
|
15
|
+
|
|
16
|
+
**MINT** is an interactive, terminal-based command center that unifies industry-standard OSINT (Open Source Intelligence) tools and a robust media archiver into a single, cohesive interface. Built for researchers, security analysts, and developers, MINT simplifies target intelligence gathering, social media investigation, and media preservation under a clean, keyboard-driven environment.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Key Features
|
|
21
|
+
|
|
22
|
+
1. **Sherlock (Username Scanner)**
|
|
23
|
+
- Scans and locates target social media accounts by username across over 300 platforms simultaneously.
|
|
24
|
+
2. **Holehe (Email Checker)**
|
|
25
|
+
- Analyzes email address registrations across more than 120 websites using password recovery endpoints, identifying registered accounts without alerting the target.
|
|
26
|
+
3. **SpiderFoot (OSINT Web Server)**
|
|
27
|
+
- Launches a local web server interface to automate security audits, domain reconnaissance, and threat intelligence gathering.
|
|
28
|
+
4. **Toutatis (Instagram Extractor)**
|
|
29
|
+
- Extracts associated emails, phone numbers, and detailed profile metadata from Instagram accounts.
|
|
30
|
+
5. **MINT Social Tool (Social Downloader)**
|
|
31
|
+
- A native, interactive downloader to archive photos, videos, stories, and highlights from major platforms (Instagram, TikTok, Facebook, and X/Twitter). Powered by high-speed `gallery-dl` and `yt-dlp` engines.
|
|
32
|
+
6. **One-Click Update Manager**
|
|
33
|
+
- Automatically pulls the latest updates directly from the official GitHub repositories for all 4 external OSINT tools and installs any updated dependencies.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Smart Path-Resolution Installer
|
|
38
|
+
|
|
39
|
+
The MINT installer (`setup.bat` / `setup.sh`) is designed to protect your file system from clutter. It prompts you once for a parent directory and automatically structures the installation cleanly:
|
|
40
|
+
* **Tools Directory:** Recreated as `<parent_folder>\MINT_Tools\` to hold all cloned OSINT scanners.
|
|
41
|
+
* **Media Directory:** Recreated as `<parent_folder>\mint-social\` to store downloaded media, profile logs, and cookies.
|
|
42
|
+
* **Global Wrappers:** Automatically registers a global `mint` command wrapper in your system path, allowing you to launch the Command Center from any terminal window.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Installation & Setup
|
|
47
|
+
|
|
48
|
+
MINT automatically manages the environment checks, directory structures, tool cloning, and dependency installations.
|
|
49
|
+
|
|
50
|
+
### Prerequisites
|
|
51
|
+
* **Python 3.10+** (Windows, macOS, or Linux)
|
|
52
|
+
* **Git** (recommended for cloning and updates; falls back to ZIP downloads if Git is missing)
|
|
53
|
+
|
|
54
|
+
### Windows Installation (Recommended)
|
|
55
|
+
1. Clone this repository to your system:
|
|
56
|
+
```bash
|
|
57
|
+
git clone https://github.com/sayfalse/mint.git
|
|
58
|
+
cd mint
|
|
59
|
+
```
|
|
60
|
+
2. Run the interactive installer by double-clicking `setup.bat` or executing:
|
|
61
|
+
```cmd
|
|
62
|
+
setup.bat
|
|
63
|
+
```
|
|
64
|
+
3. Enter your preferred parent directory (e.g., `E:\mint` or `G:\`). The installer will automatically configure and build the directory tree.
|
|
65
|
+
|
|
66
|
+
### macOS / Linux Installation
|
|
67
|
+
1. Clone the repository and navigate into it:
|
|
68
|
+
```bash
|
|
69
|
+
git clone https://github.com/sayfalse/mint.git
|
|
70
|
+
cd mint
|
|
71
|
+
```
|
|
72
|
+
2. Run the setup script:
|
|
73
|
+
```bash
|
|
74
|
+
chmod +x setup.sh
|
|
75
|
+
./setup.sh
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Package Managers
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
# YOLO
|
|
84
|
+
curl -fsSL https://raw.githubusercontent.com/sayfalse/mint/main/setup.sh | bash
|
|
85
|
+
|
|
86
|
+
# Package managers
|
|
87
|
+
pip install mint-osint # or pip install . (local install)
|
|
88
|
+
scoop install mint-osint # Windows
|
|
89
|
+
choco install mint-osint # Windows
|
|
90
|
+
brew install sayfalse/tap/mint # macOS and Linux (recommended, always up to date)
|
|
91
|
+
brew install mint-osint # macOS and Linux (official brew formula, updated less)
|
|
92
|
+
sudo pacman -S mint-osint # Arch Linux (Stable)
|
|
93
|
+
paru -S mint-osint-bin # Arch Linux (Latest from AUR)
|
|
94
|
+
nix run nixpkgs#mint-osint # or github:sayfalse/mint for latest dev branch
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## How to Run
|
|
100
|
+
|
|
101
|
+
Once setup is complete, you can launch the Command Center globally:
|
|
102
|
+
|
|
103
|
+
1. Open a new terminal window.
|
|
104
|
+
2. Type **`mint`** and press **Enter**.
|
|
105
|
+
3. Use the **Up/Down arrow keys** to navigate the menu, and **Enter** to launch your selected tool.
|
|
106
|
+
4. Alternatively, use the quick keyboard hotkeys (**1 to 7**) to jump directly to options.
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Cookie Configuration for Media Downloader
|
|
111
|
+
|
|
112
|
+
To download content from private profiles or bypass rate limits on social networks, the MINT Social Tool utilizes session cookies. The installer automatically generates 4 empty template cookie files in the correct path:
|
|
113
|
+
* `<parent_folder>\mint-social\cookies\facebook.com_cookies.txt`
|
|
114
|
+
* `<parent_folder>\mint-social\cookies\instagram.com_cookies.txt`
|
|
115
|
+
* `<parent_folder>\mint-social\cookies\tiktok.com_cookies.txt`
|
|
116
|
+
* `<parent_folder>\mint-social\cookies\x.com_cookies.txt`
|
|
117
|
+
|
|
118
|
+
### How to export and use cookies:
|
|
119
|
+
1. Install a browser extension like **Get cookies.txt LOCALLY** or **EditThisCookie** (available for Chrome/Firefox).
|
|
120
|
+
2. Log into your account on the target social network (e.g., Instagram or X).
|
|
121
|
+
3. Open the extension and export the cookies in **Netscape format**.
|
|
122
|
+
4. Open the corresponding cookie file in a text editor (e.g., `instagram.com_cookies.txt`) and paste the exported content.
|
|
123
|
+
5. Save the file. The MINT Social Tool will automatically load these cookies on subsequent runs.
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Configuration File (`config.json`)
|
|
128
|
+
|
|
129
|
+
The installer generates a `config.json` file in the MINT root directory to map all system paths dynamically. You can edit this file manually to update paths if you move directories:
|
|
130
|
+
```json
|
|
131
|
+
{
|
|
132
|
+
"tools_dir": "E:\\mint\\MINT_Tools",
|
|
133
|
+
"social_dir": "E:\\mint\\mint-social",
|
|
134
|
+
"mint_dir": "E:\\mint",
|
|
135
|
+
"mint_py_path": "E:\\mint\\mint.py",
|
|
136
|
+
"sherlock_path": "E:\\mint\\MINT_Tools\\sherlock",
|
|
137
|
+
"holehe_path": "E:\\mint\\MINT_Tools\\holehe",
|
|
138
|
+
"spiderfoot_path": "E:\\mint\\MINT_Tools\\spiderfoot",
|
|
139
|
+
"toutatis_path": "E:\\mint\\MINT_Tools\\toutatis"
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Troubleshooting & Console Setup
|
|
146
|
+
|
|
147
|
+
* **Unicode Display Issues:** If block characters or box-drawing lines appear distorted in your Windows console, MINT automatically attempts to force UTF-8 encoding on startup. If issues persist, run the following command in your terminal before launching MINT:
|
|
148
|
+
```cmd
|
|
149
|
+
chcp 65001
|
|
150
|
+
```
|
|
151
|
+
* **Git Credential Conflicts:** If the update manager fails to pull tools due to account conflicts, MINT operates a fallback mechanism that automatically downloads the latest zip archives from official repositories and extracts them cleanly, preserving your configuration.
|
|
152
|
+
* **Dependencies Failed to Install:** Ensure your terminal is running with sufficient write permissions for the target installation folders, and that Python is added to your system's PATH variable.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
mint.py,sha256=Wuqljv17hiveZEGajcs8j-d0YjiGr_KKYdXTr7ELC_M,27422
|
|
2
|
+
mint_osint-1.0.2.dist-info/METADATA,sha256=YQQQPJHYJ0NNLFa5oRmOrVQ5qCumOgzZuW2jTU8uK4A,6983
|
|
3
|
+
mint_osint-1.0.2.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
4
|
+
mint_osint-1.0.2.dist-info/entry_points.txt,sha256=S_s2lTi9-n9rgXzjSPnA6NbiNv80RjbIlVwUpzhfPcE,35
|
|
5
|
+
mint_osint-1.0.2.dist-info/top_level.txt,sha256=vtbqgpBDH8VbHkR-2RIG6cqV3ypVOgyKba9RxEVQAZI,5
|
|
6
|
+
mint_osint-1.0.2.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
mint
|