Image-based scam campaigns have become a common moderation challenge across Discord communities.
A typical campaign follows a familiar pattern:
- Either a random account joins a server, waits a few hours (sometimes days), and starts posting scam messages;
- Or a legitimate account gets compromised and suddenly begins posting the exact same scam across multiple channels.
Regardless of how the campaign starts, one characteristic remains remarkably consistent: the media attachment.
Whether it's the well-known MrBeast scam, fake nitro giveaways, or other phishing attempts, attackers rarely generate entirely new assets. Instead, the same image or GIF is repeatedly reused, occasionally with small modifications such as different text, slight color adjustments, or additional overlays.
This presents a frustrating moderation problem. Traditional text-based filters are ineffective because the spam content is embedded inside an image, while OCR introduces additional computational complexity. As a result, moderation often remains largely manual. A simpler alternative is to compare the images themselves.
The goal is not to determine whether two files are byte-for-byte identical. Instead, the objective is to determine whether two images represent the same visual content. A good fit for this problem is perceptual hashing, specifically the difference hash (dHash). The algorithm is dead simple:
- Convert the image to grayscale.
- Resize it to a fixed resolution of
(N+1) * N - Compare every pixel with its immediate neighbour.
- Encode whether the brightness increased or decreased as a single bit.
For each pixel:
where (I(x, y)) is the grayscale intensity.
The resulting sequence of bits forms a compact fingerprint describing the image's structure rather than its exact pixels.
Once both images are hashed, comparison becomes trivial. The Hamming distance tells us how many bits differ:
A normalized similarity score is then:
where:
- (d) is the Hamming distance,
- (n) is the total number of bits.
A distance of 0 means the hashes are identical, while small distances generally indicate visually similar images,
even if they were resized or lightly modified.
import numpy as np
from PIL import Image
HASH_SIZE = 16
def difference_hash(path: str) -> np.ndarray:
img = (
Image.open(path)
.convert("L")
.resize((HASH_SIZE + 1, HASH_SIZE), Image.Resampling.LANCZOS)
)
pixels = np.asarray(img, dtype=np.uint8)
return pixels[:, 1:] > pixels[:, :-1]
def compare_images(path1: str, path2: str) -> dict[str, bool | int | float]:
hash1 = difference_hash(path1)
hash2 = difference_hash(path2)
distance = int(np.count_nonzero(hash1 != hash2))
bits = hash1.size
similarity = float(1.0 - distance / bits)
return {
"match": similarity >= 0.7,
"distance": distance,
"bits": bits,
"similarity": similarity,
}The threshold is intentionally conservative for the proof of concept.
In practice it should be tuned based on real-world data and acceptable false-positive rates.
For two identical images:
{
"match": True,
"distance": 0,
"bits": 256,
"similarity": 1.0,
}For the same images with modified colours and different text:
{
"match": True,
"distance": 92,
"bits": 256,
"similarity": 0.740625,
}This is by no means a complete anti-scam solution, but it provides a lightweight signal that can be combined with behavioural heuristics to detect coordinated image-based scam campaigns with very little computational overhead.