Spaces:
Configuration error
Configuration error
GitHub Action commited on
Commit ·
9299d4e
1
Parent(s): bc8fe08
sync: chore: update various files across projects
Browse files- anticheat.db +0 -0
- test_anticheat.py +48 -0
- utils/anticheat.py +60 -55
anticheat.db
ADDED
|
Binary file (20.5 kB). View file
|
|
|
test_anticheat.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import io
|
| 2 |
+
import time
|
| 3 |
+
from PIL import Image
|
| 4 |
+
from utils.anticheat import AntiCheatEngine
|
| 5 |
+
|
| 6 |
+
def create_dummy_image_bytes(color=(255, 0, 0)):
|
| 7 |
+
img = Image.new('RGB', (100, 100), color=color)
|
| 8 |
+
img_byte_arr = io.BytesIO()
|
| 9 |
+
img.save(img_byte_arr, format='JPEG')
|
| 10 |
+
return img_byte_arr.getvalue()
|
| 11 |
+
|
| 12 |
+
def test_engine():
|
| 13 |
+
print("Initialize AntiCheatEngine...")
|
| 14 |
+
engine = AntiCheatEngine()
|
| 15 |
+
engine.clear()
|
| 16 |
+
|
| 17 |
+
print("Testing registration...")
|
| 18 |
+
img1 = create_dummy_image_bytes((255, 0, 0)) # Red image
|
| 19 |
+
|
| 20 |
+
is_dup_before = engine.is_duplicate(img1)
|
| 21 |
+
print(f"Is Duplicate before registration? {is_dup_before} (Expected: False)")
|
| 22 |
+
|
| 23 |
+
hash_str = engine.register(img1)
|
| 24 |
+
print(f"Registered hash: {hash_str}")
|
| 25 |
+
|
| 26 |
+
print(f"Total Hashes in DB: {engine.count()}")
|
| 27 |
+
|
| 28 |
+
print("Testing duplicate detection...")
|
| 29 |
+
is_dup_after = engine.is_duplicate(img1)
|
| 30 |
+
print(f"Is exact Duplicate detected? {is_dup_after} (Expected: True)")
|
| 31 |
+
|
| 32 |
+
img2 = create_dummy_image_bytes((0, 255, 0)) # Green image
|
| 33 |
+
is_diff_dup = engine.is_duplicate(img2)
|
| 34 |
+
print(f"Is totally different image detected as duplicate? {is_diff_dup} (Expected: False)")
|
| 35 |
+
|
| 36 |
+
print("Testing bulk insert performance (scalable SQLite test)...")
|
| 37 |
+
start_time = time.time()
|
| 38 |
+
for i in range(100):
|
| 39 |
+
# We don't actually generate 100 images as it's slow, just simulate fast inserts
|
| 40 |
+
with engine._get_connection() as conn:
|
| 41 |
+
conn.execute('INSERT OR IGNORE INTO hashes (hash_str) VALUES (?)', (f"fake_hash_{i}",))
|
| 42 |
+
|
| 43 |
+
print(f"Inserted 100 dummy hashes in {time.time() - start_time:.4f} seconds")
|
| 44 |
+
print(f"Final count: {engine.count()}")
|
| 45 |
+
print("SQLite AntiCheatEngine Test Passed Successfully! 🚀")
|
| 46 |
+
|
| 47 |
+
if __name__ == "__main__":
|
| 48 |
+
test_engine()
|
utils/anticheat.py
CHANGED
|
@@ -1,32 +1,41 @@
|
|
| 1 |
import os
|
| 2 |
-
import
|
| 3 |
import io
|
| 4 |
import imagehash
|
| 5 |
from PIL import Image
|
| 6 |
|
| 7 |
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
| 8 |
-
|
| 9 |
|
| 10 |
class AntiCheatEngine:
|
| 11 |
def __init__(self):
|
| 12 |
-
self.
|
| 13 |
-
self._load_hashes()
|
| 14 |
|
| 15 |
-
def
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
except Exception as e:
|
| 22 |
-
print(f"⚠️ Could not load anticheat hashes: {e}")
|
| 23 |
|
| 24 |
-
def
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
try:
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
|
|
|
| 30 |
|
| 31 |
def get_hashes(self, file_bytes):
|
| 32 |
"""Calculates pHash and dHash for better duplicate detection."""
|
|
@@ -40,59 +49,55 @@ class AntiCheatEngine:
|
|
| 40 |
|
| 41 |
def is_duplicate(self, file_bytes, similarity_threshold=8):
|
| 42 |
"""
|
| 43 |
-
Checks if the image is a duplicate
|
| 44 |
-
similarity_threshold: the max hamming distance to be considered a duplicate.
|
| 45 |
"""
|
| 46 |
p_hash_str, d_hash_str = self.get_hashes(file_bytes)
|
| 47 |
|
| 48 |
if not p_hash_str or not d_hash_str:
|
| 49 |
return False
|
| 50 |
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
# For safety, let's just do an exact match on string representations for now,
|
| 67 |
-
# or a simple distance check if we assume all stored are pHashes.
|
| 68 |
-
# Since we store both, some might be dHash, some pHash.
|
| 69 |
-
# Let's just compare distances safely.
|
| 70 |
-
distance = p_hash - stored_hash
|
| 71 |
-
if distance < similarity_threshold:
|
| 72 |
-
return True
|
| 73 |
-
|
| 74 |
-
distance = d_hash - stored_hash
|
| 75 |
-
if distance < similarity_threshold:
|
| 76 |
-
return True
|
| 77 |
-
except Exception:
|
| 78 |
-
continue
|
| 79 |
|
| 80 |
return False
|
| 81 |
|
| 82 |
def register(self, file_bytes):
|
| 83 |
"""Registers a new image hash to prevent future duplicates."""
|
| 84 |
p_hash_str, d_hash_str = self.get_hashes(file_bytes)
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
|
|
|
|
|
|
|
|
|
| 90 |
return p_hash_str
|
| 91 |
|
| 92 |
def clear(self):
|
| 93 |
-
self.
|
| 94 |
-
|
| 95 |
-
|
|
|
|
| 96 |
|
| 97 |
def count(self):
|
| 98 |
-
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import os
|
| 2 |
+
import sqlite3
|
| 3 |
import io
|
| 4 |
import imagehash
|
| 5 |
from PIL import Image
|
| 6 |
|
| 7 |
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
| 8 |
+
DB_FILE = os.path.join(BASE_DIR, 'anticheat.db')
|
| 9 |
|
| 10 |
class AntiCheatEngine:
|
| 11 |
def __init__(self):
|
| 12 |
+
self._init_db()
|
|
|
|
| 13 |
|
| 14 |
+
def _get_connection(self):
|
| 15 |
+
# Create a new connection per thread/request
|
| 16 |
+
conn = sqlite3.connect(DB_FILE)
|
| 17 |
+
# Register hamming distance function in SQLite to offload calculation
|
| 18 |
+
conn.create_function("hamming_distance", 2, self._hamming_distance)
|
| 19 |
+
return conn
|
|
|
|
|
|
|
| 20 |
|
| 21 |
+
def _init_db(self):
|
| 22 |
+
with self._get_connection() as conn:
|
| 23 |
+
conn.execute('''
|
| 24 |
+
CREATE TABLE IF NOT EXISTS hashes (
|
| 25 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 26 |
+
hash_str TEXT UNIQUE
|
| 27 |
+
)
|
| 28 |
+
''')
|
| 29 |
+
conn.execute('CREATE INDEX IF NOT EXISTS idx_hash_str ON hashes(hash_str)')
|
| 30 |
+
conn.commit()
|
| 31 |
+
|
| 32 |
+
def _hamming_distance(self, hash1_str, hash2_str):
|
| 33 |
try:
|
| 34 |
+
h1 = imagehash.hex_to_hash(hash1_str)
|
| 35 |
+
h2 = imagehash.hex_to_hash(hash2_str)
|
| 36 |
+
return h1 - h2
|
| 37 |
+
except Exception:
|
| 38 |
+
return 999
|
| 39 |
|
| 40 |
def get_hashes(self, file_bytes):
|
| 41 |
"""Calculates pHash and dHash for better duplicate detection."""
|
|
|
|
| 49 |
|
| 50 |
def is_duplicate(self, file_bytes, similarity_threshold=8):
|
| 51 |
"""
|
| 52 |
+
Checks if the image is a duplicate using SQLite optimized functions.
|
|
|
|
| 53 |
"""
|
| 54 |
p_hash_str, d_hash_str = self.get_hashes(file_bytes)
|
| 55 |
|
| 56 |
if not p_hash_str or not d_hash_str:
|
| 57 |
return False
|
| 58 |
|
| 59 |
+
with self._get_connection() as conn:
|
| 60 |
+
cursor = conn.cursor()
|
| 61 |
+
|
| 62 |
+
# 1. Fast exact match
|
| 63 |
+
cursor.execute('SELECT 1 FROM hashes WHERE hash_str = ? OR hash_str = ? LIMIT 1', (p_hash_str, d_hash_str))
|
| 64 |
+
if cursor.fetchone():
|
| 65 |
+
return True
|
| 66 |
|
| 67 |
+
# 2. Slower similarity match using the custom SQLite function
|
| 68 |
+
cursor.execute('''
|
| 69 |
+
SELECT 1 FROM hashes
|
| 70 |
+
WHERE hamming_distance(hash_str, ?) < ?
|
| 71 |
+
OR hamming_distance(hash_str, ?) < ?
|
| 72 |
+
LIMIT 1
|
| 73 |
+
''', (p_hash_str, similarity_threshold, d_hash_str, similarity_threshold))
|
| 74 |
+
|
| 75 |
+
if cursor.fetchone():
|
| 76 |
+
return True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
|
| 78 |
return False
|
| 79 |
|
| 80 |
def register(self, file_bytes):
|
| 81 |
"""Registers a new image hash to prevent future duplicates."""
|
| 82 |
p_hash_str, d_hash_str = self.get_hashes(file_bytes)
|
| 83 |
+
|
| 84 |
+
with self._get_connection() as conn:
|
| 85 |
+
if p_hash_str:
|
| 86 |
+
conn.execute('INSERT OR IGNORE INTO hashes (hash_str) VALUES (?)', (p_hash_str,))
|
| 87 |
+
if d_hash_str:
|
| 88 |
+
conn.execute('INSERT OR IGNORE INTO hashes (hash_str) VALUES (?)', (d_hash_str,))
|
| 89 |
+
conn.commit()
|
| 90 |
+
|
| 91 |
return p_hash_str
|
| 92 |
|
| 93 |
def clear(self):
|
| 94 |
+
with self._get_connection() as conn:
|
| 95 |
+
conn.execute('DELETE FROM hashes')
|
| 96 |
+
conn.commit()
|
| 97 |
+
return 0
|
| 98 |
|
| 99 |
def count(self):
|
| 100 |
+
with self._get_connection() as conn:
|
| 101 |
+
cursor = conn.cursor()
|
| 102 |
+
cursor.execute('SELECT COUNT(*) FROM hashes')
|
| 103 |
+
return cursor.fetchone()[0]
|