Skip to content

Commit 559dfc1

Browse files
committed
Switch to using i64/u64 internally for hash operations
1 parent 512ed7e commit 559dfc1

3 files changed

Lines changed: 14 additions & 20 deletions

File tree

bot/exts/filtering/_filter_lists/image_hash.py

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
log = get_logger(__name__)
1717

1818
_MAX_IMAGE_SIZE = 5_000_000
19-
_U64_MASK = (1 << 64) - 1
2019

2120

2221
class ImageHashesList(FilterList[ImageHashFilter]):
@@ -65,7 +64,7 @@ async def actions_for(
6564
log.exception("Timed out getting image hash")
6665
continue
6766

68-
image_hashes.append(image_hash & _U64_MASK)
67+
image_hashes.append(image_hash)
6968

7069
if not image_hashes:
7170
return None, [], {}
@@ -78,15 +77,9 @@ async def actions_for(
7877
actions = self[ListType.DENY].merge_actions(triggers)
7978
messages = []
8079
for filter_ in triggers:
81-
distance = self._closest_distance(filter_, image_hashes)
80+
distance = ctx.filter_info.get(filter_, "?")
8281
messages.append(
8382
f"{filter_.id} (`{filter_.content}` distance `{distance}`)"
8483
f" - {filter_.description or '*No description*'}"
8584
)
8685
return actions, messages, {ListType.DENY: triggers}
87-
88-
@staticmethod
89-
def _closest_distance(filter_: Filter, image_hashes: list[int]) -> int:
90-
"""Return the closest Hamming distance between a filter hash and any uploaded image hash."""
91-
candidate_hash = int(filter_.content, 16)
92-
return min(int.bit_count(image_hash ^ candidate_hash) for image_hash in image_hashes)

bot/exts/filtering/_filters/image_hash.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from bot.exts.filtering._filter_context import FilterContext
66
from bot.exts.filtering._filters.filter import Filter
7-
from bot.exts.filtering._image_hash import HASH_DISTANCE_THRESHOLD
7+
from bot.exts.filtering._image_hash import HASH_DISTANCE_THRESHOLD, signed_i64_to_u64
88

99
_HEX_RE = re.compile(r"^(?:0x)?([0-9a-fA-F]{1,16})$")
1010

@@ -19,8 +19,11 @@ async def triggered_on(self, ctx: FilterContext) -> bool:
1919
candidate_hash = int(self.content, 16)
2020

2121
for image_hash in ctx.content:
22-
if int.bit_count(image_hash ^ candidate_hash) <= HASH_DISTANCE_THRESHOLD:
23-
ctx.matches.append(f"{image_hash:016x}")
22+
normalized_image_hash = signed_i64_to_u64(image_hash)
23+
distance = int.bit_count(normalized_image_hash ^ candidate_hash)
24+
if distance <= HASH_DISTANCE_THRESHOLD:
25+
ctx.matches.append(f"{normalized_image_hash:016x}")
26+
ctx.filter_info[self] = str(distance)
2427
return True
2528
return False
2629

bot/exts/filtering/_image_hash.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,14 @@ async def get_image_hash(image_url: str) -> int:
2525
raise RhodiumAPIError(f"Rhodium API returned status code {response.status}: {contents}")
2626

2727
response_data = await response.json()
28-
hash_hex = response_data.get("hex")
29-
if not hash_hex:
30-
raise RhodiumAPIError("Rhodium API response did not include a hex hash.")
31-
32-
unsigned = int(str(hash_hex).removeprefix("0x"), 16)
33-
if unsigned >= (1 << 63):
34-
return unsigned - (1 << 64)
35-
return unsigned
28+
return response_data["i64"]
3629

3730

3831
def signed_i64_to_hex(value: int) -> str:
3932
"""Convert a signed 64-bit integer to a normalized lowercase 16-char hexadecimal string."""
4033
return f"{value & ((1 << 64) - 1):016x}"
34+
35+
36+
def signed_i64_to_u64(value: int) -> int:
37+
"""Convert a signed 64-bit integer into its unsigned 64-bit representation."""
38+
return value & ((1 << 64) - 1)

0 commit comments

Comments
 (0)