GitHub Action commited on
Commit
9299d4e
·
1 Parent(s): bc8fe08

sync: chore: update various files across projects

Browse files
Files changed (3) hide show
  1. anticheat.db +0 -0
  2. test_anticheat.py +48 -0
  3. 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 json
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
- HASH_FILE = os.path.join(BASE_DIR, 'anticheat_hashes.json')
9
 
10
  class AntiCheatEngine:
11
  def __init__(self):
12
- self.hashes = set()
13
- self._load_hashes()
14
 
15
- def _load_hashes(self):
16
- if os.path.exists(HASH_FILE):
17
- try:
18
- with open(HASH_FILE, 'r') as f:
19
- data = json.load(f)
20
- self.hashes = set(data.get("hashes", []))
21
- except Exception as e:
22
- print(f"⚠️ Could not load anticheat hashes: {e}")
23
 
24
- def _save_hashes(self):
 
 
 
 
 
 
 
 
 
 
 
25
  try:
26
- with open(HASH_FILE, 'w') as f:
27
- json.dump({"hashes": list(self.hashes)}, f)
28
- except Exception as e:
29
- print(f"⚠️ Could not save anticheat hashes: {e}")
 
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 based on stored hashes.
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
- # Check exact matches first for speed
52
- if p_hash_str in self.hashes or d_hash_str in self.hashes:
53
- return True
 
 
 
 
54
 
55
- p_hash = imagehash.hex_to_hash(p_hash_str)
56
- d_hash = imagehash.hex_to_hash(d_hash_str)
57
-
58
- # Check similarity (hamming distance)
59
- for stored_hash_str in self.hashes:
60
- try:
61
- stored_hash = imagehash.hex_to_hash(stored_hash_str)
62
- # Compare both pHash and dHash representation lengths isn't an issue since they are stored as strings
63
- # but we should compare apples to apples. Let's simplify and just do exact match on dHash and pHash,
64
- # but also check similarity if we parse them properly.
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
- if p_hash_str:
86
- self.hashes.add(p_hash_str)
87
- if d_hash_str:
88
- self.hashes.add(d_hash_str)
89
- self._save_hashes()
 
 
 
90
  return p_hash_str
91
 
92
  def clear(self):
93
- self.hashes.clear()
94
- self._save_hashes()
95
- return len(self.hashes)
 
96
 
97
  def count(self):
98
- return len(self.hashes)
 
 
 
 
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]