1
- # app.py
2
1
from flask import (
3
2
Flask ,
4
3
render_template ,
22
21
app = Flask (__name__ )
23
22
app .secret_key = 'your-secret-key-here'
24
23
25
- # Add background job tracking
24
+ #add background job tracking
26
25
background_jobs = {}
27
26
job_results = {}
28
27
@@ -46,16 +45,38 @@ async def maigret_search(username, options):
46
45
logger = setup_logger (logging .WARNING , 'maigret' )
47
46
try :
48
47
db = MaigretDatabase ().load_from_path (MAIGRET_DB_FILE )
49
- sites = db .ranked_sites_dict (top = int (options .get ('top_sites' , 500 )))
48
+
49
+ top_sites = int (options .get ('top_sites' ) or 500 )
50
+ if options .get ('all_sites' ):
51
+ top_sites = 999999999 # effectively all
52
+
53
+ tags = options .get ('tags' , [])
54
+ site_list = options .get ('site_list' , [])
55
+ logger .info (f"Filtering sites by tags: { tags } " )
56
+
57
+ sites = db .ranked_sites_dict (
58
+ top = top_sites ,
59
+ tags = tags ,
60
+ names = site_list ,
61
+ disabled = False ,
62
+ id_type = 'username'
63
+ )
64
+
65
+ logger .info (f"Found { len (sites )} sites matching the tag criteria" )
50
66
51
67
results = await maigret .search (
52
68
username = username ,
53
69
site_dict = sites ,
54
70
timeout = int (options .get ('timeout' , 30 )),
55
71
logger = logger ,
56
- id_type = options . get ( 'id_type' , ' username') ,
72
+ id_type = ' username' ,
57
73
cookies = COOKIES_FILE if options .get ('use_cookies' ) else None ,
58
- is_parsing_enabled = True ,
74
+ is_parsing_enabled = (not options .get ('disable_extracting' , False )),
75
+ recursive_search_enabled = (not options .get ('disable_recursive_search' , False )),
76
+ check_domains = options .get ('with_domains' , False ),
77
+ proxy = options .get ('proxy' , None ),
78
+ tor_proxy = options .get ('tor_proxy' , None ),
79
+ i2p_proxy = options .get ('i2p_proxy' , None ),
59
80
)
60
81
return results
61
82
except Exception as e :
@@ -68,36 +89,31 @@ async def search_multiple_usernames(usernames, options):
68
89
for username in usernames :
69
90
try :
70
91
search_results = await maigret_search (username .strip (), options )
71
- results .append ((username .strip (), options [ 'id_type' ] , search_results ))
92
+ results .append ((username .strip (), 'username' , search_results ))
72
93
except Exception as e :
73
94
logging .error (f"Error searching username { username } : { str (e )} " )
74
95
return results
75
96
76
97
77
98
def process_search_task (usernames , options , timestamp ):
78
99
try :
79
- # Setup event loop for async operations
80
100
loop = asyncio .new_event_loop ()
81
101
asyncio .set_event_loop (loop )
82
102
83
- # Run the search
84
103
general_results = loop .run_until_complete (
85
104
search_multiple_usernames (usernames , options )
86
105
)
87
106
88
- # Create session folder
89
107
session_folder = os .path .join (REPORTS_FOLDER , f"search_{ timestamp } " )
90
108
os .makedirs (session_folder , exist_ok = True )
91
109
92
- # Save the combined graph
93
110
graph_path = os .path .join (session_folder , "combined_graph.html" )
94
111
maigret .report .save_graph_report (
95
112
graph_path ,
96
113
general_results ,
97
114
MaigretDatabase ().load_from_path (MAIGRET_DB_FILE ),
98
115
)
99
116
100
- # Save individual reports
101
117
individual_reports = []
102
118
for username , id_type , results in general_results :
103
119
report_base = os .path .join (session_folder , f"report_{ username } " )
@@ -154,25 +170,42 @@ def process_search_task(usernames, options, timestamp):
154
170
}
155
171
)
156
172
157
- # Save results and mark job as complete
173
+ # save results and mark job as complete using timestamp as key
158
174
job_results [timestamp ] = {
159
175
'status' : 'completed' ,
160
176
'session_folder' : f"search_{ timestamp } " ,
161
177
'graph_file' : os .path .join (f"search_{ timestamp } " , "combined_graph.html" ),
162
178
'usernames' : usernames ,
163
179
'individual_reports' : individual_reports ,
164
180
}
181
+
165
182
except Exception as e :
183
+ logging .error (f"Error in search task for timestamp { timestamp } : { str (e )} " )
166
184
job_results [timestamp ] = {'status' : 'failed' , 'error' : str (e )}
167
185
finally :
168
186
background_jobs [timestamp ]['completed' ] = True
169
187
170
188
171
189
@app .route ('/' )
172
190
def index ():
173
- return render_template ('index.html' )
174
-
175
-
191
+ #load site data for autocomplete
192
+ db = MaigretDatabase ().load_from_path (MAIGRET_DB_FILE )
193
+ site_options = []
194
+
195
+ for site in db .sites :
196
+ #add main site name
197
+ site_options .append (site .name )
198
+ #add URL if different from name
199
+ if site .url_main and site .url_main not in site_options :
200
+ site_options .append (site .url_main )
201
+
202
+ #sort and deduplicate
203
+ site_options = sorted (set (site_options ))
204
+
205
+ return render_template ('index.html' , site_options = site_options )
206
+
207
+
208
+ # Modified search route
176
209
@app .route ('/search' , methods = ['POST' ])
177
210
def search ():
178
211
usernames_input = request .form .get ('usernames' , '' ).strip ()
@@ -187,15 +220,28 @@ def search():
187
220
# Create timestamp for this search session
188
221
timestamp = datetime .now ().strftime ("%Y%m%d_%H%M%S" )
189
222
190
- logging .info (f"Starting search for usernames: { usernames } " )
223
+ # Get selected tags - ensure it's a list
224
+ selected_tags = request .form .getlist ('tags' )
225
+ logging .info (f"Selected tags: { selected_tags } " )
191
226
192
227
options = {
193
- 'top_sites' : request .form .get ('top_sites' , '500' ),
194
- 'timeout' : request .form .get ('timeout' , '30' ),
195
- 'id_type' : 'username' , # fixed as username
228
+ 'top_sites' : request .form .get ('top_sites' ) or '500' ,
229
+ 'timeout' : request .form .get ('timeout' ) or '30' ,
196
230
'use_cookies' : 'use_cookies' in request .form ,
231
+ 'all_sites' : 'all_sites' in request .form ,
232
+ 'disable_recursive_search' : 'disable_recursive_search' in request .form ,
233
+ 'disable_extracting' : 'disable_extracting' in request .form ,
234
+ 'with_domains' : 'with_domains' in request .form ,
235
+ 'proxy' : request .form .get ('proxy' , None ) or None ,
236
+ 'tor_proxy' : request .form .get ('tor_proxy' , None ) or None ,
237
+ 'i2p_proxy' : request .form .get ('i2p_proxy' , None ) or None ,
238
+ 'permute' : 'permute' in request .form ,
239
+ 'tags' : selected_tags , # Pass selected tags as a list
240
+ 'site_list' : [s .strip () for s in request .form .get ('site' , '' ).split (',' ) if s .strip ()],
197
241
}
198
242
243
+ logging .info (f"Starting search for usernames: { usernames } with tags: { selected_tags } " )
244
+
199
245
# Start background job
200
246
background_jobs [timestamp ] = {
201
247
'completed' : False ,
@@ -205,46 +251,42 @@ def search():
205
251
}
206
252
background_jobs [timestamp ]['thread' ].start ()
207
253
208
- logging .info (f"Search job started with timestamp: { timestamp } " )
209
-
210
- # Redirect to status page
211
254
return redirect (url_for ('status' , timestamp = timestamp ))
212
255
213
-
214
256
@app .route ('/status/<timestamp>' )
215
257
def status (timestamp ):
216
258
logging .info (f"Status check for timestamp: { timestamp } " )
217
259
218
260
# Validate timestamp
219
261
if timestamp not in background_jobs :
220
- flash ('Invalid search session' , 'danger' )
262
+ flash ('Invalid search session.' , 'danger' )
263
+ logging .error (f"Invalid search session: { timestamp } " )
221
264
return redirect (url_for ('index' ))
222
265
223
266
# Check if job is completed
224
267
if background_jobs [timestamp ]['completed' ]:
225
268
result = job_results .get (timestamp )
226
269
if not result :
227
- flash ('No results found for this search session' , 'warning' )
270
+ flash ('No results found for this search session.' , 'warning' )
271
+ logging .error (f"No results found for completed session: { timestamp } " )
228
272
return redirect (url_for ('index' ))
229
273
230
274
if result ['status' ] == 'completed' :
231
- # Redirect to results page once done
275
+ # Note: use the session_folder from the results to redirect
232
276
return redirect (url_for ('results' , session_id = result ['session_folder' ]))
233
277
else :
234
- error_msg = result .get ('error' , 'Unknown error occurred' )
278
+ error_msg = result .get ('error' , 'Unknown error occurred. ' )
235
279
flash (f'Search failed: { error_msg } ' , 'danger' )
280
+ logging .error (f"Search failed for session { timestamp } : { error_msg } " )
236
281
return redirect (url_for ('index' ))
237
282
238
- # If job is still running, show status page with a simple spinner
283
+ # If job is still running, show a status page
239
284
return render_template ('status.html' , timestamp = timestamp )
240
285
241
286
242
287
@app .route ('/results/<session_id>' )
243
288
def results (session_id ):
244
- if not session_id .startswith ('search_' ):
245
- flash ('Invalid results session format' , 'danger' )
246
- return redirect (url_for ('index' ))
247
-
289
+ # Find completed results that match this session_folder
248
290
result_data = next (
249
291
(
250
292
r
@@ -254,6 +296,11 @@ def results(session_id):
254
296
None ,
255
297
)
256
298
299
+ if not result_data :
300
+ flash ('No results found for this session ID.' , 'danger' )
301
+ logging .error (f"Results for session { session_id } not found in job_results." )
302
+ return redirect (url_for ('index' ))
303
+
257
304
return render_template (
258
305
'results.html' ,
259
306
usernames = result_data ['usernames' ],
@@ -266,7 +313,9 @@ def results(session_id):
266
313
@app .route ('/reports/<path:filename>' )
267
314
def download_report (filename ):
268
315
try :
269
- file_path = os .path .join (REPORTS_FOLDER , filename )
316
+ file_path = os .path .normpath (os .path .join (REPORTS_FOLDER , filename ))
317
+ if not file_path .startswith (REPORTS_FOLDER ):
318
+ raise Exception ("Invalid file path" )
270
319
return send_file (file_path )
271
320
except Exception as e :
272
321
logging .error (f"Error serving file { filename } : { str (e )} " )
@@ -278,4 +327,5 @@ def download_report(filename):
278
327
level = logging .INFO ,
279
328
format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' ,
280
329
)
281
- app .run (debug = True )
330
+ debug_mode = os .getenv ('FLASK_DEBUG' , 'False' ).lower () in ['true' , '1' , 't' ]
331
+ app .run (debug = debug_mode )
0 commit comments