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 = """ + + + + + CSV Viewer + + + + + +

📂 CSV Viewer

+ {% if files %} + + {% else %} +

Brak plików w katalogu {{ dir }}.

+ {% endif %} + + {% if table %} +
+

Plik: {{ filename }}

+ {{ table | safe }} + + {% endif %} + + +""" + +@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