wysylka maili
This commit is contained in:
parent
528f31bd2e
commit
c311096e05
15
.env.example
15
.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
|
||||
SMTP_HOST=smtp.mailserver.com
|
||||
SMTP_PORT=587
|
||||
SMTP_USER=your@email.com
|
||||
SMTP_PASSWORD=yourpassword
|
||||
|
||||
DRUPAL_PATH=/path/to/drupal
|
84
README.md
Normal file
84
README.md
Normal file
@ -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.
|
159
app.py
159
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)")
|
||||
|
74
mail_template.html
Normal file
74
mail_template.html
Normal file
@ -0,0 +1,74 @@
|
||||
<!-- mail_template.html -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="pl">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Powiadomienie o nieaktywności</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Segoe UI', Roboto, sans-serif;
|
||||
background-color: #f4f4f4;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
color: #333333;
|
||||
}
|
||||
.container {
|
||||
max-width: 600px;
|
||||
margin: 40px auto;
|
||||
background-color: #ffffff;
|
||||
padding: 30px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
||||
}
|
||||
h1 {
|
||||
font-size: 22px;
|
||||
color: #222222;
|
||||
}
|
||||
p {
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.footer {
|
||||
margin-top: 30px;
|
||||
font-size: 14px;
|
||||
color: #888888;
|
||||
text-align: center;
|
||||
}
|
||||
.highlight {
|
||||
background-color: #ffe9e9;
|
||||
padding: 12px;
|
||||
border-left: 4px solid #e74c3c;
|
||||
margin: 20px 0;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Witaj @user,</h1>
|
||||
|
||||
<p>
|
||||
Kontaktujemy się z Tobą w sprawie Twojego konta w serwisie <strong>unitraklub.pl</strong>.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Zarejestrowałeś/aś konto dnia <strong>@rejestracja</strong>. Niestety, od momentu rejestracji nie wykazało ono żadnej aktywności.
|
||||
</p>
|
||||
|
||||
<div class="highlight">
|
||||
Jeśli nie zalogujesz się w najbliższym czasie, konto zostanie zablokowane i następnie usunięte.
|
||||
</div>
|
||||
|
||||
<p>
|
||||
Jeśli nadal chcesz korzystać z serwisu, wystarczy się zalogować – to wszystko!
|
||||
</p>
|
||||
|
||||
<p>Pozdrawiamy,<br>Zespół administracyjny unitraklub.pl</p>
|
||||
|
||||
<div class="footer">
|
||||
Ten e-mail został wygenerowany automatycznie. Prosimy na niego nie odpowiadać.
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
Loading…
x
Reference in New Issue
Block a user