From 4efde16fb126567f0405fa5ed79481c7c6187a4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Gruszczy=C5=84ski?= Date: Mon, 19 May 2025 09:20:09 +0200 Subject: [PATCH] =?UTF-8?q?funkcja=20wykrywania=20z=C5=82ego=20nicka?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.py | 96 ++++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 76 insertions(+), 20 deletions(-) diff --git a/app.py b/app.py index 4ade4b7..d415c1e 100644 --- a/app.py +++ b/app.py @@ -22,6 +22,7 @@ from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText import time import requests +import string os.makedirs("logs", exist_ok=True) os.makedirs("exports", exist_ok=True) @@ -103,33 +104,74 @@ def is_temp_email(email, temp_domains_cache=None): except Exception: return False +def is_bad_name(name): + if not name or len(name.strip()) < 3: + return True + + name = name.strip() + lowered = name.lower() + + # Cecha 1: tylko litery/cyfry i brak słów + entropy_chars = len(set(name)) / len(name) + digit_ratio = sum(c.isdigit() for c in name) / len(name) + alpha_ratio = sum(c.isalpha() for c in name) / len(name) + has_vowel = any(c in 'aeiouy' for c in lowered) + has_common_words = any(word in lowered for word in ['admin', 'user', 'test', 'konto', 'login', 'abc', 'name']) + + # Cecha 2: wygląda na hash/random + is_alphanum = all(c in string.ascii_letters + string.digits for c in name) + long_gibberish = is_alphanum and len(name) >= 8 and entropy_chars > 0.6 and digit_ratio > 0.3 and not has_vowel + + # Punktacja heurystyczna + score = 0 + if digit_ratio > 0.5: score += 1 + if entropy_chars > 0.7: score += 1 + if not has_vowel: score += 1 + if not has_common_words and is_alphanum and len(name) >= 8: score += 1 + if long_gibberish: score += 1 + if any(c in "!@#$%^&*()" for c in name): score += 1 + if name.lower() in {"unknown", "guest", "null"}: score += 2 + + return score >= 3 # 3+ z 6 wskazuje na losowość def export_to_csv(users): now = datetime.now().strftime("%Y-%m-%d_%H%M") 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"]) + writer.writerow([ + "UID", "Login", "E-mail", "Ostatnie logowanie", "Rejestracja", + "Punkty", "Nieaktywny", "E-mail OK", "Tymczasowy", "Zły nick" + ]) for u in users: writer.writerow([ - u['uid'], u['name'], u['mail'], + u['uid'], + u['name'], + u['mail'], datetime.fromtimestamp(u['access']).strftime('%Y-%m-%d') if u['access'] else 'nigdy', datetime.fromtimestamp(u['created']).strftime('%Y-%m-%d') if u.get('created') else 'brak', u.get('points', 0), - 'TAK' if u['inactive'] else 'NIE', - 'TAK' if u['email_valid'] else 'NIE', - 'TAK' if u['temp_email'] else 'NIE' + 'TAK' if u.get('inactive') else 'NIE', + 'TAK' if u.get('email_valid') else 'NIE', + 'TAK' if u.get('temp_email') else 'NIE', + 'TAK' if u.get('bad_name') else 'NIE', ]) print(f"📁 CSV zapisany: {filename}") + def export_to_excel(users): now = datetime.now().strftime("%Y-%m-%d_%H%M") 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"] + + headers = [ + "UID", "Login", "E-mail", "Ostatnie logowanie", "Rejestracja", + "Punkty", "Nieaktywny", "E-mail OK", "Tymczasowy", "Zły nick" + ] for col, header in enumerate(headers): sheet.write(0, col, header) + for row_idx, u in enumerate(users, start=1): sheet.write(row_idx, 0, u['uid']) sheet.write(row_idx, 1, u['name']) @@ -137,12 +179,15 @@ def export_to_excel(users): sheet.write(row_idx, 3, datetime.fromtimestamp(u['access']).strftime('%Y-%m-%d') if u['access'] else 'nigdy') sheet.write(row_idx, 4, datetime.fromtimestamp(u['created']).strftime('%Y-%m-%d') if u.get('created') else 'brak') sheet.write(row_idx, 5, u.get('points', 0)) - sheet.write(row_idx, 6, 'TAK' if u['inactive'] else 'NIE') - sheet.write(row_idx, 7, 'TAK' if u['email_valid'] else 'NIE') - sheet.write(row_idx, 8, 'TAK' if u['temp_email'] else 'NIE') + sheet.write(row_idx, 6, 'TAK' if u.get('inactive') else 'NIE') + sheet.write(row_idx, 7, 'TAK' if u.get('email_valid') else 'NIE') + sheet.write(row_idx, 8, 'TAK' if u.get('temp_email') else 'NIE') + sheet.write(row_idx, 9, 'TAK' if u.get('bad_name') else 'NIE') + workbook.close() print(f"📁 Excel zapisany: {filename}") + def flush_redis_cache(): keys = redis_client.keys("mx:*") for key in keys: @@ -170,7 +215,7 @@ def delete_user_via_php(uid, drupal_path): logging.error(f"Błąd PHP delete UID {uid}: {e.stderr}") def confirm_delete(): - answer = input("❗ Czy na pewno chcesz USUNĄĆ użytkowników? [tak/N]: ").strip().lower() + answer = input("❗ Czy na pewno chcesz ZDEZAKTYWOWAĆ użytkowników? [tak/N]: ").strip().lower() if answer not in ("tak", "t", "yes", "y"): print("❌ Operacja anulowana.") sys.exit(0) @@ -279,7 +324,7 @@ def main(): # 1. Podgląd nieaktywnych użytkowników bez punktów {script_path} --days-inactive 730 --dry-run - # 2. Usuń użytkowników z błędnymi e-mailami i nieaktywnych ponad 2 lata + # 2. Usuń (dezaktywuj) użytkowników z błędnymi e-mailami i nieaktywnych ponad 2 lata {script_path} --days-inactive 730 --delete # 3. Uwzględnij starych użytkowników (sprzed 2012), którzy logowali się w ciągu ostatnich 3 lat @@ -306,10 +351,10 @@ def main(): # 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 + # 11. Usuń (dezaktywuj) 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 + # 12. Usuń (dezaktywuj) 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) @@ -331,7 +376,7 @@ def main(): 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') + help='Usuń (dezaktywuj) 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)') @@ -370,14 +415,16 @@ def main(): help='Ile sekund czekać między paczkami maili (domyślnie: 60 sek.)') parser.add_argument('--only-invalid-emails', action='store_true', - help='Usuń tylko użytkowników z nieprawidłowymi lub tymczasowymi adresami e-mail (bez sprawdzania aktywności)') + help='Usuń (dezaktywuj) tylko użytkowników z nieprawidłowymi lub tymczasowymi adresami e-mail (bez sprawdzania aktywności)') - parser.add_argument( - "--inactive-since", - type=str, + parser.add_argument("--inactive-since", type=str, help="Zakres dni nieaktywności w formacie min-max, np. 360-1825 (tylko dla wysyłki maili)" ) + parser.add_argument('--bad-name', action='store_true', + help='Oznacz użytkowników z losowymi lub bezużytecznymi nazwami jako kandydatów do usunięcia') + + args = parser.parse_args() inactive_range = None @@ -424,6 +471,7 @@ def main(): temp_email_count = 0 skipped_with_points = 0 skipped_veterans = 0 + bad_name_count = 0 temp_domains_cache = load_temp_domains() @@ -435,6 +483,9 @@ def main(): if (user.get('post_count') or 0) > 0: continue + if user.get('bad_name'): + bad_name_count += 1 + last_access = user['access'] or 0 days_inactive = (now_ts - last_access) / 86400 @@ -445,6 +496,7 @@ def main(): user['inactive'] = is_inactive_by_days or is_inactive_by_range 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['bad_name'] = args.bad_name and is_bad_name(user['name']) if args.only_invalid_emails: if not user['email_valid']: @@ -466,7 +518,7 @@ def main(): if user['temp_email']: temp_email_count += 1 - if args.validate or user['inactive'] or not user['email_valid']: + if args.validate or user['inactive'] or not user['email_valid'] or user.get('bad_name'): final_candidates.append(user) final_candidates = [u for u in final_candidates if (u.get('points') or 0) == 0] @@ -504,6 +556,8 @@ def main(): print(f"- Nieaktywni: > {args.days_inactive} dni (~{days_to_years(args.days_inactive)} lat)") if inactive_range: print(f"- Zakres nieaktywności: {inactive_range[0]}-{inactive_range[1]} dni (~{days_to_years(inactive_range[0])}-{days_to_years(inactive_range[1])} lat)") + + print(f"- Weterani: konta przed {args.veteran_year}") print(f"- Pominięci weterani: logowanie w ostatnich {args.recent_login_days} dniach") @@ -515,6 +569,8 @@ def main(): print(f"- Z tymczasowym e-mailem: {temp_email_count}") print(f"- Kandydaci do usunięcia: {len(final_candidates)}") print(f"- Pominięci weterani: {skipped_veterans}") + print(f"- Z losowym lub podejrzanym nickiem: {bad_name_count}") + if args.delete: confirm_delete() @@ -523,7 +579,7 @@ def main(): sys.exit(1) for u in tqdm(final_candidates, desc="Usuwanie"): delete_user_via_php(u['uid'], args.drupal_path) - print(f"✅ Usunięto {len(final_candidates)} użytkowników") + print(f"✅ Zedaktywowano {len(final_candidates)} użytkowników") if __name__ == '__main__': main() \ No newline at end of file