Reference / Quickstart

MagicEyeRepostCheck — image repost detection as a Devvit library

A headless port of /u/MAGIC_EYE_BOT (downfromthetrees/the_magic_eye, MIT). No menus. No forms. No panels. Three lines of code inside your existing Devvit app's onPostSubmit trigger.

● npm: published devvit ≥ 0.11 peerDeps: @devvit/public-api no UI surface tree-shakeable SLA: p50 < 380ms
Install
npm i @magic-eye/devvit
copy

Three-line integration

Drop into any existing Devvit app. The SDK never registers a menu item, never renders a block, never opens a form. It returns a verdict; your app decides.

    main.tsTypeScript
    12 lines
  
import { Devvit } from '@devvit/public-api';
import { MagicEyeRepostCheck } from '@magic-eye/devvit';

Devvit.addTrigger({
  event: 'PostSubmit',
  async onEvent(event, ctx) {
    const verdict = await MagicEyeRepostCheck.run(event.post, ctx);
    if (verdict.isRepost && verdict.confidence > 0.92) {
      await ctx.reddit.remove(event.post.id, false);
    }
  }
});
That's the entire surface. No Devvit.addMenuItem, no Devvit.addCustomPostType, no settings UI we render for you. Your app keeps its identity; we ship a function.

Reference

FN MagicEyeRepostCheck.run(post, ctx) Perceptual-hash a submitted image & query the per-sub HashStore

Parameters

NameTypeRequiredDescription
postPostrequiredDevvit Post object from PostSubmit trigger.
ctxTriggerContextrequiredProvides redis, media, reddit. Used internally; not mutated.
options.thresholdnumberoptionalHamming distance ceiling. Default 5 (matches legacy bot).
options.windowDurationoptionalLook-back window. Default '90d'.
options.algo'dhash'|'phash'|'ahash'optionalHashing strategy. Default 'dhash'.

Returns Promise<RepostVerdict>

RepostVerdicttype
type RepostVerdict = {
  isRepost:        boolean;
  confidence:      number;       // 0..1
  hammingDistance: number | null;
  originalPostId:  string | null;
  originalAuthor:  string | null;
  algo:            'dhash' | 'phash' | 'ahash';
  suggestedAction: 'remove' | 'report' | 'flair' | 'noop';
  latencyMs:       number;
};

Example response

200 · repost
{
  "isRepost": true,
  "confidence": 0.97,
  "hammingDistance": 2,
  "originalPostId": "t3_1abx9k2",
  "originalAuthor": "u/og_poster",
  "algo": "dhash",
  "suggestedAction": "remove",
  "latencyMs": 312
}
200 · clean
{
  "isRepost": false,
  "confidence": 0.04,
  "hammingDistance": null,
  "originalPostId": null,
  "originalAuthor": null,
  "algo": "dhash",
  "suggestedAction": "noop",
  "latencyMs": 198
}

settings.json — config without UI

The SDK reads its config from devvit.yamlsettings. No Devvit.addSettings form is rendered; mods edit the YAML once at install.

devvit.yaml
name: my-mod-app
version: 0.7.2
settings:
  magicEye:
    enabled: true
    threshold: 5
    window: "90d"
    algo: dhash
    onMatch: remove     # or: report | flair | noop
    telemetry: true     # emit time-saved counter to Redis

CLI — hash-DB import / export

The legacy snoowrap bot stored hashes in MongoDB. magic-eye-cli migrates them to Devvit Redis in one command.

CLImagic-eye import <file.jsonl> --sub r/exampleBulk-load perceptual hashes into the SDK's HashStore
CLImagic-eye export --sub r/example > backup.jsonlStream a snapshot for offline analysis or backup
CLImagic-eye verify --sub r/example --sample 200Sanity-check parity vs. legacy bot's reported matches
terminal
$ magic-eye import legacy_hashes.jsonl --sub r/aww --algo dhash
  reading   412,883 records ............................. ok
  hashing   skipped (pre-computed dhash detected)
  writing   redis://devvit/r_aww/hashes ................. 412,883 / 412,883
  verify    sample=200, parity=99.5%, drift=0.5%  ........ PASS
  done      18.4s · 6.1MB redis · 0 errors

Telemetry — time-saved as data, not UI

When telemetry: true, the SDK increments two Redis counters per match: magic_eye:queue_skipped and magic_eye:seconds_saved (28s per manual repost review, configurable). The host app reads them however it wants.

read counters from anywhere in your app
const saved = await ctx.redis.get('magic_eye:seconds_saved');
// => "11538340"   (~3,205 mod-hours saved across 90 days)

Why headless?

MAGIC_EYE_BOT's value is invisible. Mods don't celebrate when reposts are caught — they only notice when their queue is quiet. A second app, with its own settings panel and its own mod-mail, adds visibility cost to an invisible benefit. Shipping it as a library means the host app's existing surface absorbs all configuration; the mod team installs one app, not two.