funkcja wykrywania złego nicka
This commit is contained in:
96
app.py
96
app.py
@ -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()
|
Reference in New Issue
Block a user