zmiany, temp maile, csv browser
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@ -6,3 +6,5 @@ __pycache__/
|
|||||||
*.pyc
|
*.pyc
|
||||||
user_cleanup_results_*
|
user_cleanup_results_*
|
||||||
venv
|
venv
|
||||||
|
exports
|
||||||
|
logs
|
98
app.py
98
app.py
@ -21,20 +21,25 @@ import smtplib
|
|||||||
from email.mime.multipart import MIMEMultipart
|
from email.mime.multipart import MIMEMultipart
|
||||||
from email.mime.text import MIMEText
|
from email.mime.text import MIMEText
|
||||||
import time
|
import time
|
||||||
|
import requests
|
||||||
|
|
||||||
|
os.makedirs("logs", exist_ok=True)
|
||||||
|
os.makedirs("exports", exist_ok=True)
|
||||||
|
|
||||||
# Redis - baza 5
|
# Redis - baza 5
|
||||||
redis_client = redis.Redis(host='localhost', port=6379, db=5, decode_responses=True)
|
redis_client = redis.Redis(host='localhost', port=6379, db=5, decode_responses=True)
|
||||||
|
|
||||||
|
script_path = os.path.abspath(__file__)
|
||||||
|
|
||||||
# Tymczasowe domeny
|
# Tymczasowe domeny
|
||||||
TEMP_DOMAINS = {
|
DISPOSABLE_DOMAINS_URL = "https://raw.githubusercontent.com/disposable-email-domains/disposable-email-domains/refs/heads/main/disposable_email_blocklist.conf"
|
||||||
"10minutemail.com", "tempmail.com", "tempmail.net", "tempmail.org",
|
DISPOSABLE_DOMAINS_CACHE_KEY = "disposable_domains:list"
|
||||||
"guerrillamail.com", "mailinator.com", "discard.email", "fakeinbox.com",
|
DISPOSABLE_DOMAINS_TTL = 86400 # 24h
|
||||||
"trashmail.com", "getnada.com", "yopmail.com", "maildrop.cc", "sharklasers.com"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Logi
|
# Logi
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
filename='user_cleanup.log',
|
filename='logs/user_cleanup.log',
|
||||||
level=logging.INFO,
|
level=logging.INFO,
|
||||||
format='%(asctime)s [%(levelname)s] %(message)s',
|
format='%(asctime)s [%(levelname)s] %(message)s',
|
||||||
datefmt='%Y-%m-%d %H:%M:%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)
|
redis_client.set(cache_key, result, ex=259200)
|
||||||
return result == "true"
|
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:
|
try:
|
||||||
domain = email.split('@')[1].lower()
|
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:
|
except Exception:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def export_to_csv(users):
|
def export_to_csv(users):
|
||||||
now = datetime.now().strftime("%Y-%m-%d_%H%M")
|
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:
|
with open(filename, mode='w', newline='', encoding='utf-8') as f:
|
||||||
writer = csv.writer(f)
|
writer = csv.writer(f)
|
||||||
writer.writerow(["UID", "Login", "E-mail", "Ostatnie logowanie", "Rejestracja", "Punkty", "Nieaktywny", "E-mail OK", "Tymczasowy"])
|
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):
|
def export_to_excel(users):
|
||||||
now = datetime.now().strftime("%Y-%m-%d_%H%M")
|
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)
|
workbook = xlsxwriter.Workbook(filename)
|
||||||
sheet = workbook.add_worksheet("Wyniki")
|
sheet = workbook.add_worksheet("Wyniki")
|
||||||
headers = ["UID", "Login", "E-mail", "Ostatnie logowanie", "Rejestracja", "Punkty", "Nieaktywny", "E-mail OK", "Tymczasowy"]
|
headers = ["UID", "Login", "E-mail", "Ostatnie logowanie", "Rejestracja", "Punkty", "Nieaktywny", "E-mail OK", "Tymczasowy"]
|
||||||
@ -248,29 +274,51 @@ def main():
|
|||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="Drupal 6 user cleanup tool",
|
description="Drupal 6 user cleanup tool",
|
||||||
epilog="""
|
epilog="""
|
||||||
Przykłady użycia:
|
Przykłady użycia:
|
||||||
|
|
||||||
# Podgląd nieaktywnych użytkowników bez punktów
|
# 1. Podgląd nieaktywnych użytkowników bez punktów
|
||||||
/root/user_manager/venv/bin/python3 app.py --days-inactive 730 --dry-run
|
{script_path} --days-inactive 730 --dry-run
|
||||||
|
|
||||||
# Usuń użytkowników z błędnymi e-mailami i nieaktywnych 2+ lata
|
# 2. Usuń użytkowników z błędnymi e-mailami i nieaktywnych ponad 2 lata
|
||||||
/root/user_manager/venv/bin/python3 app.py --days-inactive 730 --delete
|
{script_path} --days-inactive 730 --delete
|
||||||
|
|
||||||
# Uwzględnij starych użytkowników, którzy logowali się ostatnio
|
# 3. Uwzględnij starych użytkowników (sprzed 2012), którzy logowali się w ciągu ostatnich 3 lat
|
||||||
/root/user_manager/venv/bin/python3 app.py --days-inactive 730 --veteran-year 2012 --recent-login-days 1095
|
{script_path} --days-inactive 730 --veteran-year 2012 --recent-login-days 1095
|
||||||
|
|
||||||
# Tylko walidacja adresów e-mail
|
# 4. Walidacja poprawności adresów e-mail (bez usuwania)
|
||||||
/root/user_manager/venv/bin/python3 app.py --validate
|
{script_path} --validate
|
||||||
|
|
||||||
# Czyszczenie cache DNS w Redisie
|
# 5. Czyszczenie cache rekordów MX w Redisie
|
||||||
/root/user_manager/venv/bin/python3 app.py --flush-cache
|
{script_path} --flush-cache
|
||||||
|
|
||||||
# Wyślij maile tylko do użytkowników nieaktywnych od 1 do 5 lat
|
# 6. Eksportuj dane użytkowników do pliku Excel
|
||||||
/root/user_manager/venv/bin/python3 app.py --send-mails --inactive-since 365-1825
|
{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
|
formatter_class=argparse.RawDescriptionHelpFormatter
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
parser.add_argument('--host', help='Adres hosta bazy danych (można ustawić w .env jako DB_HOST)')
|
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('--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('--password', help='Hasło do bazy danych (lub DB_PASSWORD z .env)')
|
||||||
@ -377,6 +425,8 @@ def main():
|
|||||||
skipped_with_points = 0
|
skipped_with_points = 0
|
||||||
skipped_veterans = 0
|
skipped_veterans = 0
|
||||||
|
|
||||||
|
temp_domains_cache = load_temp_domains()
|
||||||
|
|
||||||
for user in tqdm(users, desc="Analiza"):
|
for user in tqdm(users, desc="Analiza"):
|
||||||
if (user.get('points') or 0) > 0:
|
if (user.get('points') or 0) > 0:
|
||||||
skipped_with_points += 1
|
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])
|
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['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']
|
user['email_valid'] = not is_fake_email(user['mail']) and not user['temp_email']
|
||||||
|
|
||||||
if args.only_invalid_emails:
|
if args.only_invalid_emails:
|
||||||
|
72
csv_browser.py
Normal file
72
csv_browser.py
Normal file
@ -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)
|
@ -5,3 +5,6 @@ python-dotenv
|
|||||||
tqdm
|
tqdm
|
||||||
redis
|
redis
|
||||||
xlsxwriter
|
xlsxwriter
|
||||||
|
requests
|
||||||
|
flask
|
||||||
|
pandas
|
Reference in New Issue
Block a user