This commit is contained in:
overcuriousity
2024-12-13 14:51:05 +01:00
parent f43ebbb6fa
commit a862309682
4 changed files with 186 additions and 134 deletions
+135 -59
View File
@@ -1,9 +1,10 @@
# app.py # app.py
from flask import Flask, render_template, request, send_file, Response, flash from flask import Flask, render_template, request, send_file, Response, flash, redirect, url_for
import logging import logging
import asyncio
import os import os
import asyncio
from datetime import datetime from datetime import datetime
from threading import Thread
import maigret import maigret
from maigret.sites import MaigretDatabase from maigret.sites import MaigretDatabase
from maigret.report import generate_report_context from maigret.report import generate_report_context
@@ -11,6 +12,10 @@ from maigret.report import generate_report_context
app = Flask(__name__) app = Flask(__name__)
app.secret_key = 'your-secret-key-here' app.secret_key = 'your-secret-key-here'
# Add background job tracking
background_jobs = {}
job_results = {}
# Configuration # Configuration
MAIGRET_DB_FILE = os.path.join('maigret', 'resources', 'data.json') MAIGRET_DB_FILE = os.path.join('maigret', 'resources', 'data.json')
COOKIES_FILE = "cookies.txt" COOKIES_FILE = "cookies.txt"
@@ -26,7 +31,7 @@ def setup_logger(log_level, name):
return logger return logger
async def maigret_search(username, options): async def maigret_search(username, options):
logger = setup_logger(logging.WARNING, 'maigret') logger = setup_logger(logging.DEBUG, 'maigret')
try: try:
db = MaigretDatabase().load_from_path(MAIGRET_DB_FILE) db = MaigretDatabase().load_from_path(MAIGRET_DB_FILE)
@@ -56,43 +61,24 @@ async def search_multiple_usernames(usernames, options):
logging.error(f"Error searching username {username}: {str(e)}") logging.error(f"Error searching username {username}: {str(e)}")
return results return results
@app.route('/') def process_search_task(usernames, options, timestamp):
def index():
return render_template('index.html')
@app.route('/search', methods=['POST'])
def search():
usernames_input = request.form.get('usernames', '').strip()
if not usernames_input:
return render_template('index.html', error="At least one username is required")
try: try:
# Split usernames by common separators # Setup event loop for async operations
usernames = [u.strip() for u in usernames_input.replace(',', ' ').split() if u.strip()] loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
# Create timestamp for this search session # Run the search
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") general_results = loop.run_until_complete(search_multiple_usernames(usernames, options))
# Create session folder
session_folder = os.path.join(REPORTS_FOLDER, f"search_{timestamp}") session_folder = os.path.join(REPORTS_FOLDER, f"search_{timestamp}")
os.makedirs(session_folder, exist_ok=True) os.makedirs(session_folder, exist_ok=True)
# Collect options from form # Save the combined graph
options = {
'top_sites': request.form.get('top_sites', '500'),
'timeout': request.form.get('timeout', '30'),
'id_type': request.form.get('id_type', 'username'),
'use_cookies': 'use_cookies' in request.form,
}
# Run search asynchronously for all usernames
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
general_results = loop.run_until_complete(search_multiple_usernames(usernames, options))
# Save the combined graph in the session folder
graph_path = os.path.join(session_folder, "combined_graph.html") graph_path = os.path.join(session_folder, "combined_graph.html")
maigret.report.save_graph_report(graph_path, general_results, MaigretDatabase().load_from_path(MAIGRET_DB_FILE)) maigret.report.save_graph_report(graph_path, general_results, MaigretDatabase().load_from_path(MAIGRET_DB_FILE))
# Save individual reports for each username # Save individual reports
individual_reports = [] individual_reports = []
for username, id_type, results in general_results: for username, id_type, results in general_results:
report_base = os.path.join(session_folder, f"report_{username}") report_base = os.path.join(session_folder, f"report_{username}")
@@ -123,46 +109,136 @@ def search():
individual_reports.append({ individual_reports.append({
'username': username, 'username': username,
'csv_file': os.path.relpath(csv_path, REPORTS_FOLDER), # Must create paths relative to REPORTS_FOLDER
'json_file': os.path.relpath(json_path, REPORTS_FOLDER), 'csv_file': os.path.join(f"search_{timestamp}", f"report_{username}.csv"),
'pdf_file': os.path.relpath(pdf_path, REPORTS_FOLDER), 'json_file': os.path.join(f"search_{timestamp}", f"report_{username}.json"),
'html_file': os.path.relpath(html_path, REPORTS_FOLDER), 'pdf_file': os.path.join(f"search_{timestamp}", f"report_{username}.pdf"),
'html_file': os.path.join(f"search_{timestamp}", f"report_{username}.html"),
'claimed_profiles': claimed_profiles, 'claimed_profiles': claimed_profiles,
}) })
return render_template( # Save results and mark job as complete
'results.html', job_results[timestamp] = {
usernames=usernames, 'status': 'completed',
graph_file=os.path.relpath(graph_path, REPORTS_FOLDER), 'session_folder': f"search_{timestamp}",
individual_reports=individual_reports, 'graph_file': os.path.join(f"search_{timestamp}", "combined_graph.html"),
timestamp=timestamp 'usernames': usernames,
) 'individual_reports': individual_reports
}
except Exception as e: except Exception as e:
logging.error(f"Error processing search: {str(e)}", exc_info=True) job_results[timestamp] = {
return render_template('index.html', error=f"An error occurred: {str(e)}") 'status': 'failed',
'error': str(e)
}
finally:
background_jobs[timestamp]['completed'] = True
@app.route('/')
def index():
return render_template('index.html')
@app.route('/search', methods=['POST'])
def search():
usernames_input = request.form.get('usernames', '').strip()
if not usernames_input:
flash('At least one username is required', 'danger')
return redirect(url_for('index'))
# Split usernames by common separators
usernames = [u.strip() for u in usernames_input.replace(',', ' ').split() if u.strip()]
# Create timestamp for this search session
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
logging.info(f"Starting search for usernames: {usernames}")
# Collect options from form
options = {
'top_sites': request.form.get('top_sites', '500'),
'timeout': request.form.get('timeout', '30'),
'id_type': request.form.get('id_type', 'username'),
'use_cookies': 'use_cookies' in request.form,
}
# Start background job
background_jobs[timestamp] = {
'completed': False,
'thread': Thread(target=process_search_task, args=(usernames, options, timestamp))
}
background_jobs[timestamp]['thread'].start()
logging.info(f"Search job started with timestamp: {timestamp}")
flash('Search started. Please wait while we process your request...', 'info')
# Redirect to status page
return redirect(url_for('status', timestamp=timestamp))
@app.route('/status/<timestamp>')
def status(timestamp):
logging.info(f"Status check for timestamp: {timestamp}")
# Validate timestamp
if timestamp not in background_jobs:
flash('Invalid search session', 'danger')
return redirect(url_for('index'))
# Check if job is completed
if background_jobs[timestamp]['completed']:
result = job_results.get(timestamp)
if not result:
flash('No results found for this search session', 'warning')
return redirect(url_for('index'))
if result['status'] == 'completed':
# Redirect to results page
return redirect(url_for('results', session_id=result['session_folder']))
else:
error_msg = result.get('error', 'Unknown error occurred')
flash(f'Search failed: {error_msg}', 'danger')
return redirect(url_for('index'))
# If job is still running, show status page
logging.info(f"Job still running for timestamp: {timestamp}")
return render_template('status.html', timestamp=timestamp)
@app.route('/results/<session_id>')
def results(session_id):
# Validate session_id format
if not session_id.startswith('search_'):
flash('Invalid results session format', 'danger')
return redirect(url_for('index'))
# Find matching result data
result_data = next(
(r for r in job_results.values()
if r.get('status') == 'completed' and r['session_folder'] == session_id),
None
)
if not result_data:
flash('Results not found or search is still in progress', 'warning')
return redirect(url_for('index'))
return render_template(
'results.html',
usernames=result_data['usernames'],
graph_file=result_data['graph_file'],
individual_reports=result_data['individual_reports'],
timestamp=session_id.replace('search_', '')
)
@app.route('/reports/<path:filename>') @app.route('/reports/<path:filename>')
def download_report(filename): def download_report(filename):
"""Serve report files""" """Serve report files"""
try: try:
return send_file(os.path.join(REPORTS_FOLDER, filename)) file_path = os.path.join(REPORTS_FOLDER, filename)
return send_file(file_path)
except Exception as e: except Exception as e:
logging.error(f"Error serving file {filename}: {str(e)}") logging.error(f"Error serving file {filename}: {str(e)}")
return "File not found", 404 return "File not found", 404
#@app.route('/view_graph/<path:graph_path>')
#def view_graph(graph_path):
# """Serve the graph HTML directly"""
# graph_file = os.path.join(REPORTS_FOLDER, graph_path)
# try:
# with open(graph_file, 'r', encoding='utf-8') as f:
# content = f.read()
# return content
# except Exception as e:
# logging.error(f"Error serving graph {graph_file}: {str(e)}")
# return "Error loading graph", 500
if __name__ == '__main__': if __name__ == '__main__':
logging.basicConfig( logging.basicConfig(
level=logging.INFO, level=logging.INFO,
+1 -1
View File
@@ -1,6 +1,6 @@
<!-- templates/base.html --> <!-- templates/base.html -->
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en" data-bs-theme="dark"></html> <html lang="en" data-bs-theme="dark">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
+24 -74
View File
@@ -1,83 +1,33 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block content %} {% block content %}
<div class="form-container"> <div class="form-container">
<h1 class="mb-4">Search Results</h1> <h1 class="mb-4">Processing Search</h1>
<p class="text-muted">Search session: {{ timestamp }}</p>
<div class="mb-4"> {% with messages = get_flashed_messages() %}
<h3>Combined Network Graph</h3> {% if messages %}
<iframe src="{{ url_for('view_graph', graph_path=graph_file) }}" width="100%" height="600px" frameborder="0"></iframe> {% for message in messages %}
</div> <div class="alert alert-info">{{ message }}</div>
<div class="mb-4">
<h3>Individual Reports</h3>
<div class="accordion" id="reportsAccordion">
{% for report in individual_reports %}
<div class="accordion-item">
<h2 class="accordion-header" id="heading{{ loop.index }}">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
data-bs-target="#collapse{{ loop.index }}" aria-expanded="false"
aria-controls="collapse{{ loop.index }}">
Results for "{{ report.username }}"
</button>
</h2>
<div id="collapse{{ loop.index }}" class="accordion-collapse collapse"
aria-labelledby="heading{{ loop.index }}" data-bs-parent="#reportsAccordion">
<div class="accordion-body">
<div class="list-group mb-3">
<a href="{{ url_for('download_report', filename=report.csv_file) }}"
class="list-group-item list-group-item-action">
Download CSV Report
</a>
<a href="{{ url_for('download_report', filename=report.json_file) }}"
class="list-group-item list-group-item-action">
Download JSON Report
</a>
<a href="{{ url_for('download_report', filename=report.html_file) }}"
class="list-group-item list-group-item-action">
Download HTML Report
</a>
<a href="{{ url_for('download_report', filename=report.pdf_file) }}"
class="list-group-item list-group-item-action">
Download PDF Report
</a>
</div>
{% if report.claimed_profiles %}
<div class="card">
<div class="card-header">
Found Profiles
</div>
<ul class="list-group list-group-flush">
{% for profile in report.claimed_profiles %}
<li class="list-group-item">
<div class="d-flex justify-content-between align-items-center">
<div>
<span class="badge bg-secondary me-2">{{ profile.site_name }}</span>
<a href="{{ profile.url }}" target="_blank" rel="noopener noreferrer">
{{ profile.url }}
</a>
</div>
{% if profile.tags %}
<div>
{% for tag in profile.tags %}
<span class="badge bg-info">{{ tag }}</span>
{% endfor %}
</div>
{% endif %}
</div>
</li>
{% endfor %}
</ul>
</div>
{% endif %}
</div>
</div>
</div>
{% endfor %} {% endfor %}
{% endif %}
{% endwith %}
<div class="text-center my-5">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span>
</div> </div>
<p class="mt-3">Processing your search request. Please wait...</p>
</div> </div>
<a href="{{ url_for('index') }}" class="btn btn-primary">New Search</a> <script>
// Check status every 2 seconds
setInterval(function() {
fetch(window.location.href)
.then(response => {
if (response.redirected) {
window.location.href = response.url;
}
});
}, 2000);
</script>
</div> </div>
{% endblock %} {% endblock %}
+26
View File
@@ -0,0 +1,26 @@
{% extends "base.html" %}
{% block content %}
<div class="container mt-4">
<div class="card">
<div class="card-body text-center">
<h2>Processing Search Request</h2>
<div class="alert alert-info">
Your search is being processed. This page will automatically redirect to the results when complete.
</div>
<div class="progress mb-3">
<div class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" style="width: 100%"></div>
</div>
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</div>
</div>
<script>
// Simple page refresh every 2 seconds
setTimeout(function() {
window.location.reload();
}, 2000);
</script>
</div>
{% endblock %}