mirror of
https://github.com/fromchat-messenger/web.git
synced 2026-09-22 19:15:08 +03:00
148 lines
4.3 KiB
Python
148 lines
4.3 KiB
Python
"""
|
|
Similarity detection utilities for username and display name comparison.
|
|
Implements both edit distance and visual similarity detection.
|
|
"""
|
|
|
|
def levenshtein_distance(s1: str, s2: str) -> int:
|
|
"""Calculate Levenshtein distance between two strings."""
|
|
if len(s1) < len(s2):
|
|
return levenshtein_distance(s2, s1)
|
|
|
|
if len(s2) == 0:
|
|
return len(s1)
|
|
|
|
previous_row = list(range(len(s2) + 1))
|
|
for i, c1 in enumerate(s1):
|
|
current_row = [i + 1]
|
|
for j, c2 in enumerate(s2):
|
|
insertions = previous_row[j + 1] + 1
|
|
deletions = current_row[j] + 1
|
|
substitutions = previous_row[j] + (c1 != c2)
|
|
current_row.append(min(insertions, deletions, substitutions))
|
|
previous_row = current_row
|
|
|
|
return previous_row[-1]
|
|
|
|
|
|
def check_visual_similarity(s1: str, s2: str) -> bool:
|
|
"""
|
|
Check if two strings are visually similar using common homoglyphs.
|
|
Returns True if strings are visually similar.
|
|
"""
|
|
if len(s1) != len(s2):
|
|
return False
|
|
|
|
# Common homoglyph mappings
|
|
homoglyphs = {
|
|
'0': ['O', 'o', 'Q'],
|
|
'O': ['0', 'o', 'Q'],
|
|
'o': ['0', 'O', 'Q'],
|
|
'1': ['l', 'I', '|'],
|
|
'l': ['1', 'I', '|'],
|
|
'I': ['1', 'l', '|'],
|
|
'5': ['S', 's'],
|
|
'S': ['5', 's'],
|
|
's': ['5', 'S'],
|
|
'6': ['G', 'g'],
|
|
'G': ['6', 'g'],
|
|
'g': ['6', 'G'],
|
|
'8': ['B', 'b'],
|
|
'B': ['8', 'b'],
|
|
'b': ['8', 'B'],
|
|
'9': ['g', 'q'],
|
|
'g': ['9', 'q'],
|
|
'q': ['9', 'g'],
|
|
'2': ['Z', 'z'],
|
|
'Z': ['2', 'z'],
|
|
'z': ['2', 'Z'],
|
|
'3': ['E'],
|
|
'E': ['3'],
|
|
'4': ['A'],
|
|
'A': ['4'],
|
|
'7': ['T', 't'],
|
|
'T': ['7', 't'],
|
|
't': ['7', 'T'],
|
|
}
|
|
|
|
for i in range(len(s1)):
|
|
c1, c2 = s1[i], s2[i]
|
|
if c1 == c2:
|
|
continue
|
|
|
|
# Check if characters are homoglyphs
|
|
if (c1 in homoglyphs and c2 in homoglyphs[c1]) or \
|
|
(c2 in homoglyphs and c1 in homoglyphs[c2]):
|
|
continue
|
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
def check_username_similarity(username1: str, username2: str) -> bool:
|
|
"""
|
|
Check if two usernames are similar using both edit distance and visual similarity.
|
|
Returns True if usernames are considered similar.
|
|
"""
|
|
if username1 == username2:
|
|
return False
|
|
|
|
# Check edit distance (Levenshtein distance <= 2)
|
|
edit_distance = levenshtein_distance(username1.lower(), username2.lower())
|
|
if edit_distance <= 2:
|
|
return True
|
|
|
|
# Check visual similarity
|
|
if check_visual_similarity(username1, username2):
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
def check_display_name_similarity(display_name1: str, display_name2: str) -> bool:
|
|
"""
|
|
Check if two display names are similar using both edit distance and visual similarity.
|
|
Returns True if display names are considered similar.
|
|
"""
|
|
if display_name1 == display_name2:
|
|
return False
|
|
|
|
# Check edit distance (Levenshtein distance <= 2)
|
|
edit_distance = levenshtein_distance(display_name1.lower(), display_name2.lower())
|
|
if edit_distance <= 2:
|
|
return True
|
|
|
|
# Check visual similarity
|
|
if check_visual_similarity(display_name1, display_name2):
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
def is_user_similar_to_verified(user_username: str, user_display_name: str,
|
|
verified_users: list[dict]) -> tuple[bool, str]:
|
|
"""
|
|
Check if a user is similar to any verified user.
|
|
|
|
Args:
|
|
user_username: Username to check
|
|
user_display_name: Display name to check
|
|
verified_users: List of verified user dictionaries with 'username' and 'display_name' keys
|
|
|
|
Returns:
|
|
Tuple of (is_similar, similar_to_username)
|
|
"""
|
|
for verified_user in verified_users:
|
|
verified_username = verified_user.get('username', '')
|
|
verified_display_name = verified_user.get('display_name', '')
|
|
|
|
# Check username similarity
|
|
if check_username_similarity(user_username, verified_username):
|
|
return True, verified_username
|
|
|
|
# Check display name similarity
|
|
if check_display_name_similarity(user_display_name, verified_display_name):
|
|
return True, verified_username
|
|
|
|
return False, ""
|