From c311096e050791144fa84150f0aa6eec974ff9d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Gruszczy=C5=84ski?= Date: Fri, 16 May 2025 23:11:46 +0200 Subject: [PATCH] wysylka maili --- .env.example | 15 +++-- README.md | 84 +++++++++++++++++++++++ app.py | 159 +++++++++++++++++++++++++++++++++++++++----- mail_template.html | 74 +++++++++++++++++++++ 4 files changed, 311 insertions(+), 21 deletions(-) create mode 100644 README.md create mode 100644 mail_template.html diff --git a/.env.example b/.env.example index 38496cf..147a0e2 100644 --- a/.env.example +++ b/.env.example @@ -1,8 +1,11 @@ -# Database connection DB_HOST=localhost -DB_USER=drupal -DB_PASSWORD=haslo -DB_NAME=drupal6 +DB_USER=root +DB_PASSWORD=secret +DB_NAME=drupal -# Path to Drupal installation (used by delete_user.php) -DRUPAL_PATH=/var/www/drupal \ No newline at end of file +SMTP_HOST=smtp.mailserver.com +SMTP_PORT=587 +SMTP_USER=your@email.com +SMTP_PASSWORD=yourpassword + +DRUPAL_PATH=/path/to/drupal \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..c3111ff --- /dev/null +++ b/README.md @@ -0,0 +1,84 @@ +# 🧹 Drupal User Cleanup & Notification Tool + +To narzędzie służy do: +- filtrowania nieaktywnych użytkowników w bazie Drupala 6, +- generowania raportów CSV/Excel, +- walidacji adresów e-mail, +- wysyłki maili ostrzegawczych przez SMTP, +- usuwania nieaktywnych kont. + +--- + +## 📦 Wymagania + +- Python 3.8+ +- Zainstalowane pakiety z `requirements.txt` +- Plik `.env` z konfiguracją SMTP i bazy danych +- Szablon HTML: `mail_template.html` + +--- + +## ⚙️ Przykładowy `.env` + +``` +DB_HOST=localhost +DB_USER=drupal_user +DB_PASSWORD=secret +DB_NAME=drupal + +SMTP_HOST=smtp.yourdomain.com +SMTP_PORT=587 +SMTP_USER=mailer@yourdomain.com +SMTP_PASSWORD=supersecret + +DRUPAL_PATH=/var/www/html +``` + +--- + +## 🚀 Przykłady użycia + +### 🔎 Tryb podglądu: +```bash +python3 app.py --days-inactive 730 --dry-run +``` + +### 🧹 Usuwanie użytkowników: +```bash +python3 app.py --days-inactive 730 --delete --drupal-path /var/www/html +``` + +### 📧 Testowy e-mail: +```bash +python3 app.py --send-test test@example.com +``` + +### 📤 Wysyłka maili do użytkowników: +```bash +python3 app.py --send-mails --mails-per-pack 100 --time-per-pack 60 +``` + +### 📊 Raport domen: +```bash +python3 app.py --report-domains +``` + +--- + +## 📁 Wyniki + +- `user_cleanup_results_YYYY-MM-DD_HHMM.csv` – lista użytkowników. +- `user_cleanup_results_YYYY-MM-DD_HHMM.xlsx` – opcjonalnie Excel. +- `user_cleanup.log` – log operacji. + +--- + +## 🛑 Uwaga + +Narzędzie **nie usuwa** użytkowników z punktami lub treściami. Filtrowanie jest ostrożne i bezpieczne. + +--- + +## ✉️ Kontakt + +Jeśli masz pytania – napisz do administratora systemu lub autora skryptu. \ No newline at end of file diff --git a/app.py b/app.py index b58907c..09d9f68 100644 --- a/app.py +++ b/app.py @@ -17,6 +17,11 @@ from tabulate import tabulate import xlsxwriter from collections import defaultdict import subprocess +import smtplib +from email.mime.multipart import MIMEMultipart +from email.mime.text import MIMEText +import time + # Redis - baza 5 redis_client = redis.Redis(host='localhost', port=6379, db=5, decode_responses=True) @@ -149,9 +154,89 @@ def confirm_delete(): def days_to_years(days): return round(days / 365, 1) +def get_smtp_config(): + required_keys = ["SMTP_HOST", "SMTP_PORT", "SMTP_USER", "SMTP_PASSWORD"] + config = {key: os.getenv(key) for key in required_keys} + + missing = [k for k, v in config.items() if not v] + if missing: + raise ValueError(f"❌ Brakuje wymaganych zmiennych SMTP w pliku .env: {', '.join(missing)}") + + return { + "host": config["SMTP_HOST"], + "port": config["SMTP_PORT"], + "user": config["SMTP_USER"], + "password": config["SMTP_PASSWORD"] + } + +def send_email_batch(users, smtp_config, mails_per_pack=100, time_per_pack=60, dry_run=False): + import os + + template_path = "mail_template.html" + if not os.path.exists(template_path): + print(f"❌ Brak pliku szablonu: {template_path}") + return + + try: + with open(template_path, "r", encoding="utf-8") as f: + template = f.read() + except Exception as e: + print(f"❌ Błąd podczas odczytu szablonu HTML: {e}") + return + + try: + smtp = smtplib.SMTP(smtp_config["host"], int(smtp_config["port"])) + smtp.starttls() + smtp.login(smtp_config["user"], smtp_config["password"]) + except Exception as e: + print(f"❌ Błąd połączenia z serwerem SMTP: {e}") + return + + print(f"📨 Rozpoczynam wysyłkę {len(users)} maili...") + + for i in tqdm(range(0, len(users), mails_per_pack), desc="Wysyłanie emaili"): + batch = users[i:i + mails_per_pack] + for user in batch: + if dry_run: + print(f"🔔 [TRYB TESTOWY] Mail do: {user['mail']}") + continue + + try: + msg = MIMEMultipart() + msg['From'] = smtp_config["user"] + msg['To'] = user['mail'] + msg['Subject'] = "Twoje konto w unitraklub.pl" + + body = template.replace("@user", user['name']).replace( + "@rejestracja", datetime.datetime.fromtimestamp(user['created']).strftime('%Y-%m-%d')) + msg.attach(MIMEText(body, 'html')) + + smtp.send_message(msg) + time.sleep(0.5) # opcjonalne mikroopóźnienie między mailami + except Exception as e: + print(f"⚠️ Błąd wysyłki do {user['mail']}: {e}") + + if i + mails_per_pack < len(users): + print(f"⏸ Przerwa {time_per_pack} sekund między paczkami...") + time.sleep(time_per_pack) + + smtp.quit() + print("✅ Wysyłka zakończona.") + +def validate_smtp_config(config): + required = ['host', 'port', 'user', 'password'] + for key in required: + if not config.get(key): + raise ValueError(f"❌ Brakuje wartości SMTP dla: {key}") + def main(): signal.signal(signal.SIGINT, lambda s, f: sys.exit("\n🛑 Przerwano przez użytkownika.")) load_dotenv() + try: + smtp_config = get_smtp_config() + except ValueError as e: + print(str(e)) + sys.exit(1) parser = argparse.ArgumentParser( description="Drupal 6 user cleanup tool", @@ -176,32 +261,70 @@ def main(): formatter_class=argparse.RawDescriptionHelpFormatter ) - parser.add_argument('--host') - parser.add_argument('--user') - parser.add_argument('--password') - parser.add_argument('--database') - parser.add_argument('--days-inactive', type=int) + 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)') + parser.add_argument('--database', help='Nazwa bazy danych (lub DB_NAME z .env)') + + parser.add_argument('--days-inactive', type=int, + help='Minimalna liczba dni nieaktywności, po której użytkownik uznawany jest za nieaktywny') + parser.add_argument('--dry-run', action='store_true', default=None, - help='Tryb podglądu (domyślny jeśli nie podano --delete)') - parser.add_argument('--delete', action='store_true') - parser.add_argument('--validate', action='store_true') - parser.add_argument('--flush-cache', action='store_true') - parser.add_argument('--export-excel', action='store_true') - parser.add_argument('--report-domains', action='store_true') + help='Tryb podglądu: nie wykonuje żadnych zmian, tylko raportuje') + + parser.add_argument('--delete', action='store_true', + help='Usuń użytkowników, którzy spełniają kryteria filtrowania') + + parser.add_argument('--validate', action='store_true', + help='Tylko sprawdź poprawność adresów e-mail (bez usuwania)') + + parser.add_argument('--flush-cache', action='store_true', + help='Wyczyść cache rekordów MX w Redisie') + + parser.add_argument('--export-excel', action='store_true', + help='Zapisz wynik filtrowania użytkowników także do pliku Excel (.xlsx)') + + parser.add_argument('--report-domains', action='store_true', + help='Wygeneruj raport ilości użytkowników według domen e-mail') parser.add_argument('--veteran-year', type=int, default=2012, - help='Minimalny rok rejestracji konta do uznania za stare (domyślnie: 2012)') + help='Rok, przed którym konto uznawane jest za stare/weterana (domyślnie: 2012)') + parser.add_argument('--recent-login-days', type=int, default=1095, - help='Ile dni wstecz ostatnie logowanie czyni konto aktywnym (domyślnie: 1095)') + help='Ile dni wstecz uznaje się zalogowanego weterana za aktywnego (domyślnie: 1095)') parser.add_argument('--show-table', action='store_true', - help='Wyświetl tabelę z listą użytkowników do usunięcia') + help='Wyświetl tabelę użytkowników spełniających kryteria do usunięcia') parser.add_argument('--drupal-path', - help="Ścieżka do katalogu Drupala (można też podać przez .env jako DRUPAL_PATH)") + help='Ścieżka do katalogu Drupala (można ustawić przez .env jako DRUPAL_PATH)') + + parser.add_argument('--send-test', metavar="EMAIL", + help='Wyślij testowego maila z szablonu na wskazany adres e-mail') + + parser.add_argument('--send-mails', action='store_true', + help='Wyślij powiadomienia e-mail do użytkowników z listy kandydatów') + + parser.add_argument('--mails-per-pack', type=int, default=100, + help='Ile e-maili wysyłać w jednej paczce (domyślnie: 100)') + + parser.add_argument('--time-per-pack', type=int, default=60, + help='Ile sekund czekać między paczkami maili (domyślnie: 60 sek.)') args = parser.parse_args() + if args.send_test: + test_user = { + 'name': 'Testowy Użytkownik', + 'mail': args.send_test, + 'created': int(datetime.datetime.now().timestamp()) - (86400 * 365 * 2) # 2 lata temu + } + + print(f"📬 Wysyłka testowego maila na: {test_user['mail']}") + send_email_batch([test_user], smtp_config, mails_per_pack=1, time_per_pack=0, dry_run=False) + return + + if not args.drupal_path: args.drupal_path = os.getenv("DRUPAL_PATH") @@ -280,9 +403,15 @@ def main(): ], headers=["UID", "Login", "E-mail", "Ostatnie log.", "Rejestracja", "Punkty", "Nieaktywny", "E-mail OK", "Tymczasowy"], tablefmt="fancy_grid")) export_to_csv(final_candidates) + + if args.export_excel: export_to_excel(final_candidates) + if args.send_mails: + send_email_batch(final_candidates, smtp_config, args.mails_per_pack, args.time_per_pack) + + print("\n📋 Parametry filtrowania:") if args.days_inactive: print(f"- Nieaktywni: brak logowania przez ≥ {args.days_inactive} dni (~{days_to_years(args.days_inactive)} lat)") diff --git a/mail_template.html b/mail_template.html new file mode 100644 index 0000000..5010683 --- /dev/null +++ b/mail_template.html @@ -0,0 +1,74 @@ + + + + + + + Powiadomienie o nieaktywności + + + +
+

Witaj @user,

+ +

+ Kontaktujemy się z Tobą w sprawie Twojego konta w serwisie unitraklub.pl. +

+ +

+ Zarejestrowałeś/aś konto dnia @rejestracja. Niestety, od momentu rejestracji nie wykazało ono żadnej aktywności. +

+ +
+ Jeśli nie zalogujesz się w najbliższym czasie, konto zostanie zablokowane i następnie usunięte. +
+ +

+ Jeśli nadal chcesz korzystać z serwisu, wystarczy się zalogować – to wszystko! +

+ +

Pozdrawiamy,
Zespół administracyjny unitraklub.pl

+ + +
+ +