diff --git a/.gitignore b/.gitignore index 4b887a9..0cd418f 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,8 @@ settings.json # other *.egg-info build +lib/vis-9.1.2/vis-network.min.js +lib/bindings/utils.js +lib/tom-select/tom-select.complete.min.js +lib/tom-select/tom-select.css +lib/vis-9.1.2/vis-network.css diff --git a/app.py b/app.py new file mode 100644 index 0000000..9a33116 --- /dev/null +++ b/app.py @@ -0,0 +1,174 @@ +# app.py +from flask import Flask, render_template, request, send_file, Response, flash +import logging +import asyncio +import os +from datetime import datetime +import maigret +from maigret.sites import MaigretDatabase +from maigret.report import generate_report_context + +app = Flask(__name__) +app.secret_key = 'your-secret-key-here' + +# Configuration +MAIGRET_DB_FILE = os.path.join('maigret', 'resources', 'data.json') +COOKIES_FILE = "cookies.txt" +UPLOAD_FOLDER = 'uploads' +REPORTS_FOLDER = 'reports' + +os.makedirs(UPLOAD_FOLDER, exist_ok=True) +os.makedirs(REPORTS_FOLDER, exist_ok=True) + +def setup_logger(log_level, name): + logger = logging.getLogger(name) + logger.setLevel(log_level) + return logger + +async def maigret_search(username, options): + logger = setup_logger(logging.WARNING, 'maigret') + + try: + db = MaigretDatabase().load_from_path(MAIGRET_DB_FILE) + sites = db.ranked_sites_dict(top=int(options.get('top_sites', 500))) + + results = await maigret.search( + username=username, + site_dict=sites, + timeout=int(options.get('timeout', 30)), + logger=logger, + id_type=options.get('id_type', 'username'), + cookies=COOKIES_FILE if options.get('use_cookies') else None, + ) + + return results + except Exception as e: + logger.error(f"Error during search: {str(e)}") + raise + +async def search_multiple_usernames(usernames, options): + results = [] + for username in usernames: + try: + search_results = await maigret_search(username.strip(), options) + results.append((username.strip(), options['id_type'], search_results)) + except Exception as e: + logging.error(f"Error searching username {username}: {str(e)}") + return results + +@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: + return render_template('index.html', error="At least one username is required") + + try: + # 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") + session_folder = os.path.join(REPORTS_FOLDER, f"search_{timestamp}") + os.makedirs(session_folder, exist_ok=True) + + # 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, + } + + # 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") + maigret.report.save_graph_report(graph_path, general_results, MaigretDatabase().load_from_path(MAIGRET_DB_FILE)) + + # Save individual reports for each username + individual_reports = [] + for username, id_type, results in general_results: + report_base = os.path.join(session_folder, f"report_{username}") + + # Save reports in different formats + csv_path = f"{report_base}.csv" + json_path = f"{report_base}.json" + pdf_path = f"{report_base}.pdf" + html_path = f"{report_base}.html" + + context = generate_report_context(general_results) + + maigret.report.save_csv_report(csv_path, username, results) + maigret.report.save_json_report(json_path, username, results, report_type='ndjson') + maigret.report.save_pdf_report(pdf_path, context) + maigret.report.save_html_report(html_path, context) + + # Extract claimed profiles + claimed_profiles = [] + for site_name, site_data in results.items(): + if (site_data.get('status') and + site_data['status'].status == maigret.result.MaigretCheckStatus.CLAIMED): + claimed_profiles.append({ + 'site_name': site_name, + 'url': site_data.get('url_user', ''), + 'tags': site_data.get('status').tags if site_data.get('status') else [] + }) + + individual_reports.append({ + 'username': username, + 'csv_file': os.path.relpath(csv_path, REPORTS_FOLDER), + 'json_file': os.path.relpath(json_path, REPORTS_FOLDER), + 'pdf_file': os.path.relpath(pdf_path, REPORTS_FOLDER), + 'html_file': os.path.relpath(html_path, REPORTS_FOLDER), + 'claimed_profiles': claimed_profiles, + }) + + return render_template( + 'results.html', + usernames=usernames, + graph_file=os.path.relpath(graph_path, REPORTS_FOLDER), + individual_reports=individual_reports, + timestamp=timestamp + ) + + except Exception as e: + logging.error(f"Error processing search: {str(e)}", exc_info=True) + return render_template('index.html', error=f"An error occurred: {str(e)}") + +@app.route('/reports/') +def download_report(filename): + """Serve report files""" + try: + return send_file(os.path.join(REPORTS_FOLDER, filename)) + except Exception as e: + logging.error(f"Error serving file {filename}: {str(e)}") + return "File not found", 404 + +@app.route('/view_graph/') +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 FileNotFoundError: + logging.error(f"Graph file not found: {graph_file}") + return "Graph not found", 404 + except Exception as e: + logging.error(f"Error serving graph {graph_file}: {str(e)}") + return "Error loading graph", 500 + +if __name__ == '__main__': + logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' + ) + app.run(debug=True) \ No newline at end of file diff --git a/maigret/resources/data.json b/maigret/resources/data.json index 70c07c1..04e1528 100644 --- a/maigret/resources/data.json +++ b/maigret/resources/data.json @@ -17474,7 +17474,7 @@ "method": "vimeo" }, "headers": { - "Authorization": "jwt eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MzM5Njc3MjAsInVzZXJfaWQiOm51bGwsImFwcF9pZCI6NTg0NzksInNjb3BlcyI6InB1YmxpYyIsInRlYW1fdXNlcl9pZCI6bnVsbCwianRpIjoiNGJkNDE4NzktM2VhOS00ZWRiLWIzZDUtNjAyNjQ3YjMyNTVhIn0.kPbKREujSfYsisyF0pS_HskTapRlHBfVLRw4cis1ezk" + "Authorization": "jwt eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MzQwMzc5MjAsInVzZXJfaWQiOm51bGwsImFwcF9pZCI6NTg0NzksInNjb3BlcyI6InB1YmxpYyIsInRlYW1fdXNlcl9pZCI6bnVsbCwianRpIjoiM2U2ZWQ1MDYtZTU0OC00ZGIwLWI4YTMtMzdiZWMyYzRiYTJiIn0.vojHtXWsDNBtjQjoVm6DSV9XHhWzu-PUMwjOJouMkG8" }, "urlProbe": "https://api.vimeo.com/users/{username}?fields=name%2Cgender%2Cbio%2Curi%2Clink%2Cbackground_video%2Clocation_details%2Cpictures%2Cverified%2Cmetadata.public_videos.total%2Cavailable_for_hire%2Ccan_work_remotely%2Cmetadata.connections.videos.total%2Cmetadata.connections.albums.total%2Cmetadata.connections.followers.total%2Cmetadata.connections.following.total%2Cmetadata.public_videos.total%2Cmetadata.connections.vimeo_experts.is_enrolled%2Ctotal_collection_count%2Ccreated_time%2Cprofile_preferences%2Cmembership%2Cclients%2Cskills%2Cproject_types%2Crates%2Ccategories%2Cis_expert%2Cprofile_discovery%2Cwebsites%2Ccontact_emails&fetch_user_profile=1", "checkType": "status_code", diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..4800874 --- /dev/null +++ b/templates/base.html @@ -0,0 +1,44 @@ + + + + + + +Maigret Web Interface + + + + +
+
+ +
+ {% block content %}{% endblock %} +
+ + + + diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..3218200 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,35 @@ +{% extends "base.html" %} +{% block content %} +
+

Maigret Web Interface

+ + {% if error %} +
{{ error }}
+ {% endif %} + +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ + +
+
+{% endblock %} \ No newline at end of file diff --git a/templates/results.html b/templates/results.html new file mode 100644 index 0000000..a1ab091 --- /dev/null +++ b/templates/results.html @@ -0,0 +1,83 @@ +{% extends "base.html" %} +{% block content %} +
+

Search Results

+

Search session: {{ timestamp }}

+ +
+

Combined Network Graph

+ +
+ +
+

Individual Reports

+
+ {% for report in individual_reports %} +
+

+ +

+
+
+ + + {% if report.claimed_profiles %} +
+
+ Found Profiles +
+
    + {% for profile in report.claimed_profiles %} +
  • +
    +
    + {{ profile.site_name }} + + {{ profile.url }} + +
    + {% if profile.tags %} +
    + {% for tag in profile.tags %} + {{ tag }} + {% endfor %} +
    + {% endif %} +
    +
  • + {% endfor %} +
+
+ {% endif %} +
+
+
+ {% endfor %} +
+
+ + New Search +
+{% endblock %} \ No newline at end of file