Skip to content

Repository files navigation

Bot or Not

A self-hosted LAN game where participants guess whether images are AI-generated or real photographs. Designed for classroom and lab events — no internet required, runs entirely on your local network.


Table of Contents


How It Works

Participant                    Server                         Admin
    │                             │                              │
    │  Open browser → /           │                              │
    │  Enter name + confirm  ───► │  POST /api/login             │
    │                        ◄─── │  Sets session cookie         │
    │                             │  Starts countdown timer      │
    │  Game starts                │                              │
    │  Image shown           ◄─── │  GET /api/next-image         │
    │  Click "Bot" or "Not"  ───► │  POST /api/answer            │
    │                        ◄─── │  Returns correct/wrong       │
    │  ... repeats until time up  │  Writes to responses.jsonl   │
    │                             │  Updates leaderboard.json    │
    │  Redirected to /results     │                              │
    │  View leaderboard      ◄─── │  GET /api/leaderboard        │
    │                             │                              │
    │                             │          ◄────────────────── │  GET /admin
    │                             │          ◄────────────────── │  See live sessions, scores, logs
    │                             │          ◄────────────────── │  Kick players, adjust duration
    │                             │          ◄────────────────── │  Rebuild leaderboard / export CSV

Features

  • Zero internet dependency — fully self-hosted on your LAN
  • Opaque image IDs — folder structure (bot/not) is never exposed to clients
  • Live leaderboard — auto-refreshes every 10 seconds
  • Admin dashboard — live sessions, logs, config, kick players, export CSV
  • Quit mid-game — with confirmation dialog
  • Start confirmation — two-step flow before the timer begins
  • Session timer — configurable per-event from the admin panel
  • Batch-safe — same PC can be used by multiple participants back-to-back
  • Dot-folder exclusion — rename any image folder to .foldername to exclude it without deleting
  • Rebuild leaderboard — reconstruct from raw logs if leaderboard.json is edited or corrupted
  • CSV export — download all responses for offline analysis

Project Structure

botOrNot/
├── server.js                  # Express server — all API routes
├── config.json                # Runtime config (session duration)
├── package.json
├── .env                       # Secrets — DO NOT commit (see .env.example)
├── .env.example               # Template for environment variables
│
├── scripts/
│   └── build-manifest.js      # One-time script: scans images, generates data/manifest.json
│
├── public/                    # Static frontend (served by Express)
│   ├── index.html             # Login / start page
│   ├── game.html              # Main game (image + Bot/Not buttons)
│   ├── results.html           # Post-game score breakdown
│   ├── leaderboard.html       # Public live leaderboard
│   ├── admin-login.html       # Admin login page
│   ├── admin.html             # Admin dashboard
│   └── style.css              # Shared stylesheet
│
├── images/                    # Your image dataset (gitignored — manage locally)
│   └── datasets/
│       ├── bot/               # AI-generated images go here
│       └── not/               # Real photographs go here
│
└── data/                      # Generated at runtime (gitignored)
    ├── manifest.json          # ID → file path mapping (generated by build-manifest.js)
    ├── responses.jsonl        # Append-only log of every answer
    └── leaderboard.json       # Live leaderboard (updated on every answer)

Requirements

  • Node.js v18 or higher
  • npm v8 or higher
  • A machine connected to your LAN (wired recommended for stability)
  • Your image dataset organized into bot/ and not/ folders

Check your Node version:

node --version

Setup & Deployment

1. Clone the repo

git clone https://github.com/yourusername/botOrNot.git
cd botOrNot

2. Install dependencies

npm install

3. Add your images

Place your images into the correct folders:

images/
└── datasets/
    ├── bot/        ← AI-generated images (any depth of subfolders is fine)
    └── not/        ← Real photographs
  • Supported formats: .jpg, .jpeg, .png, .webp, .gif, .bmp, .tiff, .avif
  • Subfolders are scanned recursively — you can organize freely inside bot/ and not/
  • To exclude a subfolder without deleting it, prefix its name with a dot: .excluded_folder

4. Configure environment

Copy the example file and fill in your values:

cp .env.example .env

Edit .env:

ADMIN_PASSWORD=yourpassword

# Generate a secure random secret:
# npm run generate-secret
SESSION_SECRET=paste_generated_secret_here

Generate a session secret:

npm run generate-secret

5. Build the manifest

This scans your image folders and creates data/manifest.json — a mapping of opaque random IDs to real file paths. Run this once before the event. Do not re-run it during an active event (it changes all image IDs and breaks active sessions).

npm run build-manifest

Output example:

[INFO] Scanning image directories...
[INFO] Found 80 bot images, 9 real images.
[INFO] Assigning IDs to bot images...
[INFO] Assigning IDs to real images...
[INFO] Writing manifest.json...
[DONE] Manifest written: 89 total entries.
       80 bot, 9 real.
       File size: 0.0 MB

No files were copied or renamed. Your dataset is untouched.
You can now run: node server.js

To regenerate with new IDs (e.g. after adding new images):

npm run build-manifest-force

⚠️ --force changes all image IDs. Do this only before an event starts, never mid-competition.

6. Set session duration

Edit config.json to set how many minutes each participant gets:

{
  "sessionDurationMinutes": 5
}

You can also change this live from the Admin Dashboard without restarting the server. Changes apply to new sessions only — participants already playing keep their original duration.

7. Start the server

npm start

You'll see:

┌─────────────────────────────────────────────┐
│         Bot or Not — Server Running          │
├─────────────────────────────────────────────┤
│  Local:   http://localhost:6767              │
│  LAN:     http://192.168.1.42:6767           │
│  Admin:   http://192.168.1.42:6767/admin-login.html │
├─────────────────────────────────────────────┤
│  Images: 89 total (80 bot, 9 real)           │
└─────────────────────────────────────────────┘

For development with auto-restart on file changes:

npm run dev

Image Dataset Format

images/datasets/
├── bot/                        # Anything inside is treated as AI-generated
│   ├── dalle/
│   │   ├── image1.png
│   │   └── image2.jpg
│   ├── midjourney/
│   │   └── img.webp
│   └── .excluded_set/          # Dot-prefix = skipped entirely
│       └── ...
│
└── not/                        # Anything inside is treated as real
    ├── unsplash/
    │   └── photo.jpg
    └── flickr/
        └── real_photo.png

Key rules:

  • The bot/ and not/ folder names are the labels — everything inside is labeled accordingly
  • Sub-folder names are irrelevant and never shown to participants
  • Any folder starting with . is silently skipped at manifest build time
  • Small or corrupt image files are included — if they can't load in the browser, the game shows "Image failed to load" and continues

Configuration

config.json

Controls the session timer. Editable live from the Admin Dashboard.

Field Type Default Description
sessionDurationMinutes number 1 How long each participant's session lasts

.env

Variable Required Description
ADMIN_PASSWORD Yes Plaintext password for the admin dashboard
SESSION_SECRET Yes Random string to sign the admin cookie
PORT No Server port (default: 6767)

Admin Dashboard

Navigate to http://<your-ip>:6767/admin-login.html

What you can do:

Section What it shows / does
Overview Total participants, answers, correct count, overall accuracy
Live Sessions Currently active players — name, IP, images seen, time remaining
Kick Terminate any player's session instantly (with confirmation)
Settings Change session duration for future logins
Rebuild Leaderboard Recalculate leaderboard.json from responses.jsonl (with confirmation)
All Responses Full log of every answer — newest first
Download CSV Export responses.jsonl as a CSV for offline analysis

The dashboard auto-polls every 5 seconds for sessions and 10 seconds for logs.


Scoring

  • Correct answer: +10 points
  • Wrong answer: 0 points
  • Leaderboard is sorted by points (descending), then by total answered (descending) as a secondary sort

The leaderboard is updated live on every answer — no need to refresh.


Data & Logs

data/responses.jsonl

Append-only log — one JSON object per line, one per answer. Never modified in-place.

{"username":"Alice","privateIp":"192.168.1.5","imageId":"a3f1bc...","trueLabel":"bot","choice":"bot","correct":true,"pointsAwarded":10,"timestamp":"2025-07-15T12:00:01.000Z","responseTimeMs":2341}
{"username":"Alice","privateIp":"192.168.1.5","imageId":"d92c4a...","trueLabel":"not","choice":"bot","correct":false,"pointsAwarded":0,"timestamp":"2025-07-15T12:00:04.000Z","responseTimeMs":1102}

data/leaderboard.json

Updated on every answer. Keyed by username.

{
  "Alice": {
    "username": "Alice",
    "privateIp": "192.168.1.5",
    "totalAnswered": 12,
    "correctCount": 9,
    "points": 90,
    "lastUpdated": "2025-07-15T12:01:44.000Z"
  }
}

If this file gets corrupted (e.g. manual edit gone wrong), use the Rebuild Leaderboard button in the admin dashboard to regenerate it cleanly from responses.jsonl.

data/manifest.json

Maps random IDs → real file paths. Generated by build-manifest.js. Never expose this file publicly — it contains the real paths and labels of every image.


Multiple Batches / Shared PCs

The game is designed for lab environments where the same computer is used by multiple participants in sequence:

  1. When a participant finishes, they are sent to the results page
  2. The next participant opens http://<server-ip>:6767/ (the login page) in the browser
  3. Opening the login page clears the previous player's local session data automatically
  4. The new participant enters their name and plays fresh

Username conflict: Each username must be unique within an active session window. If a player's timer has expired, their name is freed and can be reused. If two batches overlap and someone wants to reuse an active name, the server returns an error — they should pick a different name or wait.

Participant IP tracking: The server records each participant's LAN IP address (privateIp) in all logs and the leaderboard. This is captured from the request on login — it does not require any input from the participant.


How the Manifest Works

The manifest is the core security mechanism that prevents cheating:

  1. build-manifest.js scans images/datasets/bot/ and images/datasets/not/
  2. Each image gets a random 16-character hex ID (e.g. a3f1bc8e72d04c91)
  3. The manifest stores: { id, label, filename, _path }server-side only
  4. When a participant requests an image, the server serves it at /api/image/<id>
  5. The browser only ever sees the opaque ID — never the path, folder name, or label
  6. The label (bot/not) is only revealed after the participant submits their answer

This means:

  • Participants cannot look at the URL and know the answer
  • No files are copied or renamed — zero extra disk usage
  • The dataset remains completely untouched

API Reference

Participant endpoints

Method Path Auth Description
POST /api/login None Start a session. Body: { username }
POST /api/logout Cookie Clear session cookie
GET /api/next-image Cookie Get next image ID and URL
GET /api/image/:id None Serve image bytes (ETag cached)
POST /api/answer Cookie Submit answer. Body: { imageId, choice, responseTimeMs }
GET /api/session-status Cookie Get remaining session time
POST /api/quit Cookie End session early
GET /api/leaderboard None Get current sorted leaderboard

Admin endpoints (require admin cookie)

Method Path Description
POST /api/admin/login Authenticate admin. Body: { password }
GET /api/admin/logs All responses + summary stats
GET /api/admin/config Get current config
POST /api/admin/config Update session duration
GET /api/admin/active-sessions List active player sessions
POST /api/admin/kick-session Terminate a player session. Body: { username }
POST /api/admin/rebuild-leaderboard Rebuild leaderboard.json from responses.jsonl
GET /api/admin/export Download all responses as CSV

Troubleshooting

Server won't start — "ADMIN_PASSWORD is not set"

You haven't created a .env file. Run:

cp .env.example .env
# then edit .env and set ADMIN_PASSWORD and SESSION_SECRET

Server won't start — "data/manifest.json not found"

You haven't run the manifest builder yet:

npm run build-manifest

Server won't start — "No bot images found" / "No real images found"

Your image folders are empty or in the wrong location. Check that:

  • images/datasets/bot/ contains at least one image
  • images/datasets/not/ contains at least one image
  • Image files have supported extensions (.jpg, .png, .webp, etc.)

Participants can't connect

  • Make sure the server is bound to 0.0.0.0 (it is by default)
  • Check your firewall isn't blocking port 6767
  • On Linux: sudo ufw allow 6767
  • Share the LAN IP shown at startup, not localhost

Can't log in to admin dashboard

  • Check ADMIN_PASSWORD in your .env matches what you're typing
  • Passwords are case-sensitive

Images not loading in the game

  • Check that data/manifest.json exists and was generated from the correct image paths
  • If you moved the images/ folder after building the manifest, regenerate: npm run build-manifest-force

"Username already in use" error

The previous participant's session hasn't expired yet, or the admin hasn't kicked it. Options:

  • Wait for the timer to expire
  • Admin: open the dashboard and click Kick next to that player's row
  • Use a different username

Leaderboard shows wrong scores

The leaderboard.json may be out of sync (e.g. after a manual edit). Use Rebuild Leaderboard in the admin dashboard — it recalculates everything from responses.jsonl which is the authoritative source.

Server crashed mid-event

All answers written to responses.jsonl before the crash are safe (append-only file). Active session state (who was playing, their timers) is lost since it's in-memory. Restart the server — participants will need to log in again. Use Rebuild Leaderboard after restart to restore scores.


License

MIT

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages