funkcja wykrywania złego nicka

This commit is contained in:
Mateusz Gruszczyński
2025-05-19 09:20:09 +02:00
parent 9c001eb404
commit 4efde16fb1

96
app.py
View File

@ -22,6 +22,7 @@ from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText from email.mime.text import MIMEText
import time import time
import requests import requests
import string
os.makedirs("logs", exist_ok=True) os.makedirs("logs", exist_ok=True)
os.makedirs("exports", exist_ok=True) os.makedirs("exports", exist_ok=True)
@ -103,33 +104,74 @@ def is_temp_email(email, temp_domains_cache=None):
except Exception: except Exception:
return False 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): 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"exports/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", "Zły nick"
])
for u in users: for u in users:
writer.writerow([ 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['access']).strftime('%Y-%m-%d') if u['access'] else 'nigdy',
datetime.fromtimestamp(u['created']).strftime('%Y-%m-%d') if u.get('created') else 'brak', datetime.fromtimestamp(u['created']).strftime('%Y-%m-%d') if u.get('created') else 'brak',
u.get('points', 0), u.get('points', 0),
'TAK' if u['inactive'] else 'NIE', 'TAK' if u.get('inactive') else 'NIE',
'TAK' if u['email_valid'] else 'NIE', 'TAK' if u.get('email_valid') else 'NIE',
'TAK' if u['temp_email'] else 'NIE' 'TAK' if u.get('temp_email') else 'NIE',
'TAK' if u.get('bad_name') else 'NIE',
]) ])
print(f"📁 CSV zapisany: {filename}") print(f"📁 CSV zapisany: {filename}")
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"exports/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", "Zły nick"
]
for col, header in enumerate(headers): for col, header in enumerate(headers):
sheet.write(0, col, header) sheet.write(0, col, header)
for row_idx, u in enumerate(users, start=1): for row_idx, u in enumerate(users, start=1):
sheet.write(row_idx, 0, u['uid']) sheet.write(row_idx, 0, u['uid'])
sheet.write(row_idx, 1, u['name']) 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, 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, 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, 5, u.get('points', 0))
sheet.write(row_idx, 6, 'TAK' if u['inactive'] else 'NIE') sheet.write(row_idx, 6, 'TAK' if u.get('inactive') else 'NIE')
sheet.write(row_idx, 7, 'TAK' if u['email_valid'] else 'NIE') sheet.write(row_idx, 7, 'TAK' if u.get('email_valid') else 'NIE')
sheet.write(row_idx, 8, 'TAK' if u['temp_email'] 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() workbook.close()
print(f"📁 Excel zapisany: {filename}") print(f"📁 Excel zapisany: {filename}")
def flush_redis_cache(): def flush_redis_cache():
keys = redis_client.keys("mx:*") keys = redis_client.keys("mx:*")
for key in keys: 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}") logging.error(f"Błąd PHP delete UID {uid}: {e.stderr}")
def confirm_delete(): 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"): if answer not in ("tak", "t", "yes", "y"):
print("❌ Operacja anulowana.") print("❌ Operacja anulowana.")
sys.exit(0) sys.exit(0)
@ -279,7 +324,7 @@ def main():
# 1. Podgląd nieaktywnych użytkowników bez punktów # 1. Podgląd nieaktywnych użytkowników bez punktów
{script_path} --days-inactive 730 --dry-run {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 {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 # 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 # 10. Wyślij testowego maila na podany adres
{script_path} --send-test test@example.com {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 {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 {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) # 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') help='Tryb podglądu: nie wykonuje żadnych zmian, tylko raportuje')
parser.add_argument('--delete', action='store_true', 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', parser.add_argument('--validate', action='store_true',
help='Tylko sprawdź poprawność adresów e-mail (bez usuwania)') 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.)') help='Ile sekund czekać między paczkami maili (domyślnie: 60 sek.)')
parser.add_argument('--only-invalid-emails', action='store_true', 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( parser.add_argument("--inactive-since", type=str,
"--inactive-since",
type=str,
help="Zakres dni nieaktywności w formacie min-max, np. 360-1825 (tylko dla wysyłki maili)" 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() args = parser.parse_args()
inactive_range = None inactive_range = None
@ -424,6 +471,7 @@ def main():
temp_email_count = 0 temp_email_count = 0
skipped_with_points = 0 skipped_with_points = 0
skipped_veterans = 0 skipped_veterans = 0
bad_name_count = 0
temp_domains_cache = load_temp_domains() temp_domains_cache = load_temp_domains()
@ -435,6 +483,9 @@ def main():
if (user.get('post_count') or 0) > 0: if (user.get('post_count') or 0) > 0:
continue continue
if user.get('bad_name'):
bad_name_count += 1
last_access = user['access'] or 0 last_access = user['access'] or 0
days_inactive = (now_ts - last_access) / 86400 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['inactive'] = is_inactive_by_days or is_inactive_by_range
user['temp_email'] = is_temp_email(user['mail'], temp_domains_cache) 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']
user['bad_name'] = args.bad_name and is_bad_name(user['name'])
if args.only_invalid_emails: if args.only_invalid_emails:
if not user['email_valid']: if not user['email_valid']:
@ -466,7 +518,7 @@ def main():
if user['temp_email']: if user['temp_email']:
temp_email_count += 1 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.append(user)
final_candidates = [u for u in final_candidates if (u.get('points') or 0) == 0] 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)") print(f"- Nieaktywni: > {args.days_inactive} dni (~{days_to_years(args.days_inactive)} lat)")
if inactive_range: 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"- 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"- Weterani: konta przed {args.veteran_year}")
print(f"- Pominięci weterani: logowanie w ostatnich {args.recent_login_days} dniach") 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"- Z tymczasowym e-mailem: {temp_email_count}")
print(f"- Kandydaci do usunięcia: {len(final_candidates)}") print(f"- Kandydaci do usunięcia: {len(final_candidates)}")
print(f"- Pominięci weterani: {skipped_veterans}") print(f"- Pominięci weterani: {skipped_veterans}")
print(f"- Z losowym lub podejrzanym nickiem: {bad_name_count}")
if args.delete: if args.delete:
confirm_delete() confirm_delete()
@ -523,7 +579,7 @@ def main():
sys.exit(1) sys.exit(1)
for u in tqdm(final_candidates, desc="Usuwanie"): for u in tqdm(final_candidates, desc="Usuwanie"):
delete_user_via_php(u['uid'], args.drupal_path) 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__': if __name__ == '__main__':
main() main()