Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

2 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ“š aghpb.cc

Bringing anime girls holding programming books, and literally any other image, onto your ComputerCraft screens!

Bun TypeScript CC%3ATweaked SQLite License

This is a two-part project: a Bun-powered image proxy that converts any web image (or a random book from the AGHPB API) into a ComputerCraft-native format, and a Lua client that talks to that proxy from inside CC:Tweaked and renders the result on any terminal or monitor, auto-fit to its size.

No re-fetching nor re-converting as every image is content-hashed and perceptually-hashed on the way in, so the exact same image (or a near-duplicate served from a different URL) is served straight from cache instead of being downloaded and reprocessed all over again.

Written in Bun/TypeScript on the server side with no bloated image libraries, decoding/resizing goes through sharp only, everything else (palette matching, hashing, caching, HTTP) is hand-rolled. The client is plain CC:Tweaked Lua with zero dependencies beyond the built-in http and paintutils APIs.

🌐 Supported AGHPB instances

The proxy tries instances in order until one responds, so a single dead instance never breaks /aghpb/random.

Country URL Hosted by Notes
πŸ‡«πŸ‡· https://aghpb.thenolle.com Nolly ⭐ Default instance
πŸ‡¬πŸ‡§ https://api.devgoldy.xyz/aghpb/v1 DevGoldy Official instance
πŸ‡ΈπŸ‡ͺ https://aghpb.zeeraa.net Zeeraa Backup

Edit AGHPB_INSTANCES in src/aghpb.ts to reorder, add, or remove instances -> no config file needed.

✨ Features

  • Any image, any URL -> /convert turns literally any web-hosted image into a ComputerCraft-drawable format
  • Random AGHPB books -> /aghpb/random, optionally filtered by category, fully wired into the same conversion pipeline
  • Two output formats -> nfp (a paintutils-loadable paint-format image) or blit (a JSON row array of text/fg/bg for direct term.blit rendering)
  • Auto-fit rendering -> the Lua client detects whether it's running on a monitor or the bare terminal, reads its exact size, and requests the image pre-sized to fit so no scaling math on the CC side for faster renders
  • Exact-match caching -> every converted image is content-hashed (SHA-256); the same URL, or the same bytes served from a different URL, is served from cache with zero re-fetch and zero re-convert
  • Perceptual-hash dedup -> a difference-hash (dHash) catches near-identical images (recompressed, re-hosted, slightly resized) and reuses the existing cached conversion instead of doing the work again
  • 16-color palette matching -> every pixel is nearest-neighbor matched (Euclidean RGB distance) against ComputerCraft's default palette
  • SQLite-backed cache -> all cache state lives in a single cache.sqlite file via bun:sqlite, with per-image hit counters
  • No bloated image libraries -> decoding/resizing goes through sharp only; hashing, palette matching, and NFP/blit generation are all hand-written

πŸ“¦ Installation

Proxy (Bun server)

  1. Clone the repo and cd into it
    git clone https://github.com/thenolle/aghpb.cc.git
    cd aghpb.cc/proxy
  2. Install dependencies
    bun install
  3. Start the server
    bun run dev   # with --watch, for development
    # or
    bun run start # for production
  4. It listens on http://localhost:3000 by default (override with PORT=xxxx)

Client (ComputerCraft)

  1. Copy client.lua, viewer.lua, and aghpb.lua onto your CC:Tweaked computer (via a disk, or whatever gets files onto your machine, I personally use vscode + computer-craft extension)
  2. Edit client.lua's BASE_URL to point at your proxy's real reachable address (see Networking caveats below - localhost almost never works from inside a CC computer)
  3. Allow that address in your server's http.rules config (see HTTP rules)
  4. Run viewer <imageUrl> or aghpb [category]

Requirements

  • Bun 1.3.x
  • CC:Tweaked 1.120.x+ (any version with http, paintutils, and textutils.unserialiseJSON)
  • http API enabled on the CC server, with a rule allowing your proxy's address

πŸ•ΉοΈ Client scripts

Script Description
client.lua Shared module that builds requests, talks HTTP to the proxy, decodes NFP/blit responses. Required by the other two
viewer.lua <imageUrl> Renders any image URL, converted through /convert, auto-fit to the current screen
aghpb.lua [category] Fetches a random AGHPB book (optionally by category) through /aghpb/random and renders it

Examples

viewer https://i.imgur.com/mOF9W7D.png
aghpb
aghpb typescript

Both scripts auto-detect an attached monitor (wrapping the first one found) and fall back to the local terminal if none exists, reading its exact getSize() and requesting the image pre-converted to those dimensions.

🌐 Proxy API

Endpoint Description
GET /convert?url=<imageUrl>&width=&height=&format=nfp|blit Convert any image URL into a CC-drawable format
GET /aghpb/random?category=&width=&height=&format= Convert a random AGHPB book, with Book-* metadata as response headers
GET /aghpb/get/id/:searchId?width=&height=&format= Convert a specific AGHPB book by its search ID
GET /aghpb/categories List all AGHPB categories (proxied straight through)
GET /aghpb/search?query=&category=&limit= Search AGHPB books by name
GET /aghpb/info Stats about the currently-responding AGHPB instance
GET /health Liveness check

width/height default to 50Γ—19 if omitted. Every image response carries x-cache (url-hit / sha256-hit / phash-hit / miss) and x-image-sha256 headers so you can see exactly why a request was fast or slow.

πŸ—οΈ How it works

Conversion pipeline

  1. /convert first checks the SQLite cache for this exact URL at this exact width/height/format - if found, nothing is fetched at all
  2. On a miss, the source image is fetched and SHA-256 hashed; if those exact bytes were already converted (e.g. served from a different, mirrored, or redirected URL), the cached conversion is reused and the new URL is linked to it
  3. On another miss, a difference-hash (dHash, 8Γ—8 grayscale gradient, 64-bit) is computed and compared via Hamming distance against every cached image of the same dimensions/format - a close-enough match (recompressed, slightly resized, or re-hosted copy of the same picture) is reused instead of reconverting
  4. Only if nothing matches does the image actually get resized and every pixel nearest-neighbor matched against ComputerCraft's 16-color palette, producing either an .nfp paint-format grid or a term.blit-ready JSON row array
  5. The result, its SHA-256, and its dHash are all written back to cache.sqlite, and the requesting URL is linked to it for instant future hits

AGHPB integration

/aghpb/random and /aghpb/get/id/:id share the exact same cache/convert pipeline, just entering it from an already-fetched buffer instead of a URL (since /random has no stable URL to key off of). They skip straight to the SHA-256/dHash checks so the same book resurfacing from a later random pull, or the same book fetched by ID twice, still hits the cache instantly.

Perceptual hashing

The dHash algorithm resizes the source to a 9Γ—8 grayscale grid and encodes whether each pixel is brighter or darker than its right-hand neighbor into a 64-bit fingerprint. It's cheap to compute, resistant to resizing/recompression noise, and comparing two images is just a Hamming distance over 64 bits - no perceptual-hash library required.

Why do I bother explaining all of that, do anyone read it even ? If you do, you are a nerd and I love you.

πŸ” HTTP rules

CC:Tweaked blocks requests to private/local IP ranges by default. If your proxy runs on the same LAN as your Minecraft server (or the same machine), add an explicit allow rule in your server config:

[http]
  [[http.rules]]
    host = "*"
    max_upload = 4194304
    action = "allow"
    use_proxy = false
    max_websocket_message = 131072
    max_download = 33554432

  [[http.rules]] # replaces the old `$private` rule that blocks LAN access
    host = "192.168.x.x" # your proxy's actual LAN IP
    action = "allow"

⚠️ Networking caveats

localhost/127.0.0.1 inside client.lua's BASE_URL refers to the Minecraft server process's own loopback, not the CC computer's. Unless the proxy and the Minecraft server are the exact same process space, point BASE_URL at the proxy machine's real LAN (or public) address, and make sure that address (not 127.0.0.1) is what's allowed in http.rules.

πŸ› οΈ Building / project layout

proxy/
  src/
    index.ts       # entry point
    server.ts      # Bun.serve routes (/convert, /aghpb/*, /health)
    convert.ts     # fetch + sharp resize + palette mapping -> NFP/blit
    cacheFlow.ts   # shared sha256 -> phash -> convert -> cache pipeline
    phash.ts       # dHash computation + Hamming distance
    palette.ts     # ComputerCraft 16-color palette + nearest-match
    database.ts    # bun:sqlite cache schema + queries
    aghpb.ts       # AGHPB API client with instance fallback
client.lua         # shared CC HTTP/decode module
viewer.lua         # renders any image URL
aghpb.lua          # renders a random AGHPB book

Bun runs the TypeScript directly so no more build step [you can tho if you want to, it just isn't necessary], now you can just bun run dev and it will work.

πŸ’« Credits

πŸ“œ License

WTFPL - Do whatever the f*ck you want


Made with 🩡 for anime girls and glorious 16-color monitors

About

Bringing Anime Girls Holding Programming Books, and literally any other image, onto your ComputerCraft screens!

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Sponsor this project

Contributors

Languages