From dedaeeba3f76e5061b78a9631b237c9ca1087feb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mateusz=20Gruszczy=C5=84ski?=
 <mateusz.gruszczynski@firma.interia.pl>
Date: Mon, 19 May 2025 08:41:25 +0200
Subject: [PATCH] zmiany, temp maile, csv browser

---
 .gitignore       |  4 +-
 app.py           | 98 ++++++++++++++++++++++++++++++++++++------------
 csv_browser.py   | 72 +++++++++++++++++++++++++++++++++++
 requirements.txt |  3 ++
 4 files changed, 152 insertions(+), 25 deletions(-)
 create mode 100644 csv_browser.py

diff --git a/.gitignore b/.gitignore
index 33cd283..f826587 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,4 +5,6 @@
 __pycache__/
 *.pyc
 user_cleanup_results_*
-venv
\ No newline at end of file
+venv
+exports
+logs
\ No newline at end of file
diff --git a/app.py b/app.py
index 77e2f92..4ade4b7 100644
--- a/app.py
+++ b/app.py
@@ -21,20 +21,25 @@ import smtplib
 from email.mime.multipart import MIMEMultipart
 from email.mime.text import MIMEText
 import time
+import requests
+
+os.makedirs("logs", exist_ok=True)
+os.makedirs("exports", exist_ok=True)
 
 # Redis - baza 5
 redis_client = redis.Redis(host='localhost', port=6379, db=5, decode_responses=True)
 
+script_path = os.path.abspath(__file__)
+
 # Tymczasowe domeny
-TEMP_DOMAINS = {
-    "10minutemail.com", "tempmail.com", "tempmail.net", "tempmail.org",
-    "guerrillamail.com", "mailinator.com", "discard.email", "fakeinbox.com",
-    "trashmail.com", "getnada.com", "yopmail.com", "maildrop.cc", "sharklasers.com"
-}
+DISPOSABLE_DOMAINS_URL = "https://raw.githubusercontent.com/disposable-email-domains/disposable-email-domains/refs/heads/main/disposable_email_blocklist.conf"
+DISPOSABLE_DOMAINS_CACHE_KEY = "disposable_domains:list"
+DISPOSABLE_DOMAINS_TTL = 86400  # 24h
+
 
 # Logi
 logging.basicConfig(
-    filename='user_cleanup.log',
+    filename='logs/user_cleanup.log',
     level=logging.INFO,
     format='%(asctime)s [%(levelname)s] %(message)s',
     datefmt='%Y-%m-%d %H:%M:%S'
@@ -71,16 +76,37 @@ def is_fake_email(email):
     redis_client.set(cache_key, result, ex=259200)
     return result == "true"
 
-def is_temp_email(email):
+def load_temp_domains():
+    cached = redis_client.get(DISPOSABLE_DOMAINS_CACHE_KEY)
+    if cached:
+        return set(cached.split(","))
+
+    try:
+        resp = requests.get(DISPOSABLE_DOMAINS_URL, timeout=10)
+        if resp.status_code == 200:
+            domains = {line.strip().lower() for line in resp.text.splitlines() if line.strip() and not line.startswith("#")}
+            redis_client.set(DISPOSABLE_DOMAINS_CACHE_KEY, ",".join(domains), ex=DISPOSABLE_DOMAINS_TTL)
+            return domains
+        else:
+            print(f"⚠️ Nie udało się pobrać listy disposable domains (kod {resp.status_code})")
+    except Exception as e:
+        print(f"⚠️ Błąd pobierania listy disposable domains: {e}")
+
+    return set()
+
+def is_temp_email(email, temp_domains_cache=None):
     try:
         domain = email.split('@')[1].lower()
-        return domain in TEMP_DOMAINS
+        if temp_domains_cache is None:
+            temp_domains_cache = load_temp_domains()
+        return domain in temp_domains_cache
     except Exception:
         return False
 
+
 def export_to_csv(users):
     now = datetime.now().strftime("%Y-%m-%d_%H%M")
-    filename = f"user_cleanup_results_{now}.csv"
+    filename = f"exports/user_cleanup_results_{now}.csv"
     with open(filename, mode='w', newline='', encoding='utf-8') as f:
         writer = csv.writer(f)
         writer.writerow(["UID", "Login", "E-mail", "Ostatnie logowanie", "Rejestracja", "Punkty", "Nieaktywny", "E-mail OK", "Tymczasowy"])
@@ -98,7 +124,7 @@ def export_to_csv(users):
 
 def export_to_excel(users):
     now = datetime.now().strftime("%Y-%m-%d_%H%M")
-    filename = f"user_cleanup_results_{now}.xlsx"
+    filename = f"exports/user_cleanup_results_{now}.xlsx"
     workbook = xlsxwriter.Workbook(filename)
     sheet = workbook.add_worksheet("Wyniki")
     headers = ["UID", "Login", "E-mail", "Ostatnie logowanie", "Rejestracja", "Punkty", "Nieaktywny", "E-mail OK", "Tymczasowy"]
@@ -248,29 +274,51 @@ def main():
     parser = argparse.ArgumentParser(
         description="Drupal 6 user cleanup tool",
         epilog="""
-    Przykłady użycia:
+            Przykłady użycia:
 
-    # Podgląd nieaktywnych użytkowników bez punktów
-    /root/user_manager/venv/bin/python3 app.py --days-inactive 730 --dry-run
+            # 1. Podgląd nieaktywnych użytkowników bez punktów
+            {script_path} --days-inactive 730 --dry-run
 
-    # Usuń użytkowników z błędnymi e-mailami i nieaktywnych 2+ lata
-    /root/user_manager/venv/bin/python3 app.py --days-inactive 730 --delete
+            # 2. Usuń użytkowników z błędnymi e-mailami i nieaktywnych ponad 2 lata
+            {script_path} --days-inactive 730 --delete
 
-    # Uwzględnij starych użytkowników, którzy logowali się ostatnio
-    /root/user_manager/venv/bin/python3 app.py --days-inactive 730 --veteran-year 2012 --recent-login-days 1095
+            # 3. Uwzględnij starych użytkowników (sprzed 2012), którzy logowali się w ciągu ostatnich 3 lat
+            {script_path} --days-inactive 730 --veteran-year 2012 --recent-login-days 1095
 
-    # Tylko walidacja adresów e-mail
-    /root/user_manager/venv/bin/python3 app.py --validate
+            # 4. Walidacja poprawności adresów e-mail (bez usuwania)
+            {script_path} --validate
 
-    # Czyszczenie cache DNS w Redisie
-    /root/user_manager/venv/bin/python3 app.py --flush-cache
+            # 5. Czyszczenie cache rekordów MX w Redisie
+            {script_path} --flush-cache
 
-    # Wyślij maile tylko do użytkowników nieaktywnych od 1 do 5 lat
-    /root/user_manager/venv/bin/python3 app.py --send-mails --inactive-since 365-1825
+            # 6. Eksportuj dane użytkowników do pliku Excel
+            {script_path} --days-inactive 730 --dry-run --export-excel
+
+            # 7. Wygeneruj raport liczby użytkowników wg domen e-mail
+            {script_path} --days-inactive 730 --report-domains
+
+            # 8. Wyświetl tabelę z użytkownikami kwalifikującymi się do usunięcia
+            {script_path} --days-inactive 730 --show-table
+
+            # 9. Wyślij e-maile do użytkowników nieaktywnych od 1 do 5 lat
+            {script_path} --send-mails --inactive-since 365-1825
+
+            # 10. Wyślij testowego maila na podany adres
+            {script_path} --send-test test@example.com
+
+            # 11. Usuń tylko użytkowników z nieprawidłowym lub tymczasowym adresem e-mail
+            {script_path} --only-invalid-emails --delete
+
+            # 12. Usuń nieaktywnych użytkowników, z ustawioną ścieżką do Drupala
+            {script_path} --days-inactive 730 --delete --drupal-path /var/www/drupal
+
+            # 13. Ustaw inną liczbę maili i przerwę między paczkami (np. 50 maili co 30s)
+            {script_path} --send-mails --inactive-since 730-2000 --mails-per-pack 50 --time-per-pack 30
     """,
         formatter_class=argparse.RawDescriptionHelpFormatter
     )
 
+
     parser.add_argument('--host', help='Adres hosta bazy danych (można ustawić w .env jako DB_HOST)')
     parser.add_argument('--user', help='Użytkownik bazy danych (lub DB_USER z .env)')
     parser.add_argument('--password', help='Hasło do bazy danych (lub DB_PASSWORD z .env)')
@@ -377,6 +425,8 @@ def main():
     skipped_with_points = 0
     skipped_veterans = 0
 
+    temp_domains_cache = load_temp_domains()
+
     for user in tqdm(users, desc="Analiza"):
         if (user.get('points') or 0) > 0:
             skipped_with_points += 1
@@ -393,7 +443,7 @@ def main():
         is_inactive_by_range = inactive_range and (inactive_range[0] <= days_inactive <= inactive_range[1])
         
         user['inactive'] = is_inactive_by_days or is_inactive_by_range
-        user['temp_email'] = is_temp_email(user['mail'])
+        user['temp_email'] = is_temp_email(user['mail'], temp_domains_cache)
         user['email_valid'] = not is_fake_email(user['mail']) and not user['temp_email']
 
         if args.only_invalid_emails:
diff --git a/csv_browser.py b/csv_browser.py
new file mode 100644
index 0000000..dcacbc7
--- /dev/null
+++ b/csv_browser.py
@@ -0,0 +1,72 @@
+import os
+from flask import Flask, render_template_string, send_from_directory, request
+import pandas as pd
+
+EXPORTS_DIR = "exports"
+PORT = 8899
+
+app = Flask(__name__)
+
+TEMPLATE = """
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>CSV Viewer</title>
+    <link rel="stylesheet"
+        href="https://cdn.datatables.net/1.13.4/css/jquery.dataTables.min.css">
+    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
+    <script
+        src="https://cdn.datatables.net/1.13.4/js/jquery.dataTables.min.js"></script>
+</head>
+<body>
+    <h2>📂 CSV Viewer</h2>
+    {% if files %}
+        <ul>
+        {% for file in files %}
+            <li><a href="/view?file={{ file }}">{{ file }}</a></li>
+        {% endfor %}
+        </ul>
+    {% else %}
+        <p>Brak plików w katalogu <code>{{ dir }}</code>.</p>
+    {% endif %}
+
+    {% if table %}
+        <hr>
+        <h3>Plik: {{ filename }}</h3>
+        {{ table | safe }}
+        <script>
+            $(document).ready(function() {
+                $('#csv-table').DataTable();
+            });
+        </script>
+    {% endif %}
+</body>
+</html>
+"""
+
+@app.route("/")
+def index():
+    files = [f for f in os.listdir(EXPORTS_DIR) if f.endswith(".csv")]
+    return render_template_string(TEMPLATE, files=files, table=None, dir=EXPORTS_DIR)
+
+@app.route("/view")
+def view_file():
+    filename = request.args.get("file")
+    if not filename or not filename.endswith(".csv"):
+        return "Niepoprawny plik.", 400
+    path = os.path.join(EXPORTS_DIR, filename)
+    if not os.path.exists(path):
+        return "Plik nie istnieje.", 404
+
+    try:
+        df = pd.read_csv(path)
+        html_table = df.to_html(classes="display", index=False, table_id="csv-table")
+        files = [f for f in os.listdir(EXPORTS_DIR) if f.endswith(".csv")]
+        return render_template_string(TEMPLATE, files=files, table=html_table, filename=filename, dir=EXPORTS_DIR)
+    except Exception as e:
+        return f"Błąd odczytu CSV: {e}", 500
+
+if __name__ == "__main__":
+    os.makedirs(EXPORTS_DIR, exist_ok=True)
+    app.run(host="0.0.0.0", port=PORT)
diff --git a/requirements.txt b/requirements.txt
index b26cd57..1a887f1 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -5,3 +5,6 @@ python-dotenv
 tqdm
 redis
 xlsxwriter
+requests
+flask
+pandas
\ No newline at end of file