Jesse A. Green / notebook

Live demo: the DocCompiler guide library as a static, read-only snapshot (October 2026). No backend, no accounts, nothing to submit.

foobar2000-catbox — Administrator & Developer Guide

Generated May 2026 from realoksi/foobar2000-catbox at f47db1a

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

1. Architecture Overview

foobar2000-catbox is a single-binary Rust command-line tool. It accepts one image file path on stdin, processes the image in memory (optional resize, optional re-encode), then performs a multipart HTTP POST to either catbox.moe or litterbox.catbox.moe and writes the returned URL to stdout.

There is no daemon, no server, no database, and no persistent state beyond the settings.yml file placed adjacent to the binary.

stdin (file path)
        │
        ▼
    main.rs
        │── load settings.yml (sibling to executable)
        │── validate settings
        │── read image file from path
        │── [optional] resize (nearest-neighbor, aspect-preserving)
        │── [optional] re-encode (JPG / PNG / WebP)
        ▼
    consumer.rs
        │── Catbox  → POST https://catbox.moe/user/api.php
        │── Litterbox → POST https://litterbox.catbox.moe/resources/internals/api.php
        ▼
stdout (URL string)

Note

Settings are loaded from the directory of the executable (env::current_exe().parent()), not the process working directory. This is a deliberate design choice so the binary and its config can be co-located and invoked from any working directory.

Component Inventory

Component File Role
Entry point src/main.rs Orchestrates the full pipeline: settings load → validate → read → process → upload
Upload backends src/consumer.rs Catbox and Litterbox structs implementing the Consumer trait
Configuration src/settings.rs Settings, EncodeFormat, ExpireTime types; YAML deserialization and validation
Config file settings.yml User-editable YAML configuration (co-located with binary)
Sample images samples/ Test images at various resolutions (512–2048 px), used by integration tests

Technology Stack

Crate Version Purpose
image 0.25.6 Image decoding (format detection) and encoding (JPG, PNG, WebP)
curl 0.4.47 HTTP multipart POST upload
serde 1.0.219 Derive macros for YAML deserialization
serde_yml 0.0.12 YAML parser for settings.yml
strum / strum_macros 0.27.1 EnumString derive for EncodeFormat and ExpireTime
base64 0.22.1 Used in test fixtures to embed a 1×1 PNG inline

Data Flow

  1. main() resolves settings.yml path as current_exe().parent().join("settings.yml").
  2. File is read with fs::read_to_string; failures fall back to empty string → default Settings.
  3. Settings::from_str() deserializes YAML; failures fall back to Settings::new().
  4. settings.validate() checks bounds; any violation prints a colored error and exits.
  5. One line is read from stdin via io::stdin().lock().lines().next().
  6. The file is read into a Vec<u8>; format is detected with image::guess_format.
  7. Optional resize: DynamicImage::resize() with FilterType::Nearest.
  8. Optional encode: write_with_encoder() to a Cursor<Vec<u8>>.
  9. The in-memory buffer is handed to consumer.upload_image().
  10. The returned URL string is printed to stdout; curl errors exit with code 6.

2. System Requirements

Item Requirement
Rust edition 2021
Minimum Rust version Compatible with Rust stable (no nightly features used)
libcurl Required at link time by the curl crate (0.4.47)
Build target Any rustup target — release binary is statically linked where toolchain supports
Network Outbound HTTPS to catbox.moe (port 443) and/or litterbox.catbox.moe (port 443)

Note

The release profile (Cargo.toml) sets strip = true, lto = true, codegen-units = 16. Stripped LTO binaries are significantly smaller and produce no debug symbols — do not attempt to attach a debugger to a release build.


3. Configuration Reference

settings.yml — Complete Field Reference

The file is YAML, deserialized via serde_yml. Unknown keys are silently collected into unexpected: HashMap<String, Value> (no error on extra keys).

Key Type Default Valid Values Validation
enable_litterbox bool false true, false None
litterbox_expire_time string "24h" "1h", "12h", "24h", "72h" YAML parse error if unrecognized
enable_encode bool false true, false None
encode_format string "JPG" "JPG" (also "JPEG"), "PNG", "WEBP" YAML parse error if unrecognized
encode_format_quality u8 80 1–100 Exit code 1 if out of range
enable_resize bool false true, false None
resize_max_resolution [u32, u32] [1024, 1024] Positive powers of two Exit code 2 (zero) or 3 (not power-of-two)
user_hash string "" 25-char string or empty Exit code 4 if non-empty and not 25 chars
user_agent string Firefox 133 UA Any string None

Important

encode_format values are case-insensitive at parse time (ascii_case_insensitive via strum). "jpg", "JPG", "jPg" all map to the JPG variant. However, litterbox_expire_time values are case-sensitive — only lowercase "1h", "12h", "24h", "72h" are accepted.

Validation Error Details

The Settings::validate() method enforces these constraints and returns a typed Error variant:

Exit Code Error Variant Message
1 EncodeFormatQualityError(u8) encode_format_quality is out-of-bounds (N)
2 ResizeMaxResolutionZeroError([u32; 2]) resize_max_resolution values must be larger than 0 (W, H)
3 ResizeMaxResolutionPowerError([u32; 2]) both values of resize_max_resolution must be a power-of-two (W, H)
4 UserHashError(u8) user_hash isn't 25 characters (N)

Note

Exit code 4 is shared between UserHashError (startup validation) and "file not found" (runtime). Callers cannot distinguish them from the exit code alone — stderr content is the differentiator.


4. Deployment & Scaling

Deployment Checklist

  • [x] Build release binary: cargo build --release
  • [x] Place binary and settings.yml in the same directory
  • [x] Verify settings.yml reflects desired defaults
  • [x] Test: echo "/path/to/test.jpg" | ./foobar2000-catbox → URL printed
  • [ ] If using user_hash, verify it is exactly 25 characters
  • [ ] If firewall-restricted, open outbound HTTPS to catbox.moe and litterbox.catbox.moe

Invocation Patterns

Shell pipe:

echo "/mnt/music/album/cover.jpg" | /opt/catbox-uploader/foobar2000-catbox

Capture URL in variable:

URL=$(echo "/path/to/image.png" | ./foobar2000-catbox)

Process substitution (foobar2000 component integration): The binary is intended to be called from a media player component that writes the current track's album art path to a pipe or file. The component passes the path via stdin; the binary outputs the URL the component uses for display (e.g. Discord Rich Presence image URL).

Note

The binary is single-shot: one path in, one URL out, then exit. For high-frequency invocation (e.g. track changes), each call is a fresh process — no connection pooling (easy.forbid_reuse(true) is set explicitly in the curl backend).

Concurrent Invocations

No shared state exists. Multiple processes can run simultaneously without interference. Each reads its own copy of settings.yml at startup.


5. Operations

Monitoring

There is no logging facility. The binary uses:

  • stdout — success URL
  • stderr — error messages (colored with ANSI escape codes on non-Windows terminals)
  • Exit codes — see the table in Section 3

To capture both streams in a wrapper script:

OUTPUT=$(echo "$PATH_TO_ART" | ./foobar2000-catbox 2>/tmp/catbox_err.log)
EXIT=$?
if [ $EXIT -ne 0 ]; then
    cat /tmp/catbox_err.log
fi

Tip

Wrap invocations in a script that checks the exit code and logs stderr. The binary itself has no retry logic — build retries at the integration layer if needed.

Running Tests

The project includes integration tests that make real HTTP requests:

cargo test

Warning

test_consumers in src/consumer.rs uploads a real 1×1 PNG to catbox.moe and litterbox.catbox.moe. These tests require internet access and hit the live services. Do not run them in CI environments without network access or rate-limit considerations.

test_perform verifies curl connectivity — it asserts https://example.com/ succeeds and http://localhost/ fails (connection refused). This also requires internet access.

Updating settings.yml

Changes to settings.yml take effect on the next invocation. There is no reload signal — the file is read fresh each time the binary starts.


6. Security

Network Exposure

The binary makes outbound POST requests to:

  • https://catbox.moe/user/api.php (Catbox)
  • https://litterbox.catbox.moe/resources/internals/api.php (Litterbox)

No inbound ports are opened. No local server is started. All communication is HTTPS.

Caution

Images uploaded to catbox.moe (non-litterbox) are permanently public. Anyone with the URL can access the image. Do not upload images containing sensitive content unless you intend them to be publicly accessible.

User Hash Sensitivity

The user_hash value is a credential that associates uploads with a catbox account. Store settings.yml with appropriate filesystem permissions if this value is configured:

chmod 600 settings.yml

Input Validation

  • File path is read from stdin (first line only, trimmed).
  • The file's existence is checked before reading.
  • Image format is detected from file contents via image::guess_format, not the file extension — mitigates extension-spoofing concerns for image format detection.
  • settings.yml unknown keys are silently ignored (collected into unexpected map) — no arbitrary code execution from config.

Note

The binary does not sanitize the file path beyond checking existence. If the binary is called by a component that constructs paths from user-controlled input, the caller is responsible for path sanitization.

User-Agent

The default user-agent identifies as Firefox on Linux. It can be changed freely in settings.yml. The curl backend sends it as a custom User-Agent HTTP header.


7. Developer Internals

Repository Structure

foobar2000-catbox/
├── Cargo.toml          Package manifest (version 1.0.9, edition 2021)
├── Cargo.lock          Pinned dependency tree
├── LICENSE             License file
├── README.md           (not present in briefing — no repo_docs extracted)
├── settings.yml        Example/default config co-located with binary
├── samples/            Test images: 512.jpg, 1024.jpg, 1205.jpg, 1456.jpg, 1805.jpg, 2048.jpg
└── src/
    ├── main.rs         Entry point, pipeline orchestrator
    ├── consumer.rs     Upload backends (Catbox, Litterbox) + Consumer trait
    └── settings.rs     Settings struct, enums, validation, YAML deserialization

Module Reference

src/main.rs — Entry Point

main() is the sole function. Execution order:

  1. Resolve settings.yml path via env::current_exe().parent().join("settings.yml")
  2. Deserialize settings (double fallback: parse error → Settings::new())
  3. Validate settings — exits 1–4 on failure
  4. Read one line from stdin
  5. Verify file exists
  6. Read file bytes
  7. Decode with image::load_from_memory
  8. Conditional resize (nearest-neighbor via DynamicImage::resize)
  9. Conditional encode into Cursor<Vec<u8>>
  10. Construct Box<dyn Consumer> based on enable_litterbox
  11. Call consumer.upload_image(bytes) → print URL or exit 6

src/consumer.rs — Upload Backends

Consumer trait:

pub trait Consumer {
    fn upload_image(&self, data: Vec<u8>) -> Result<String, Error>;
}

Catbox struct:

  • Fields: user_agent: String, user_hash: String
  • Endpoint: https://catbox.moe/user/api.php
  • Form fields: reqtype=fileupload, fileToUpload=<bytes>, userhash=<hash> (if non-empty)
  • File name in multipart form: auto-detected from image::guess_format → extensions_str()[0]

Litterbox struct:

  • Fields: expire_time: String, user_agent: String, user_hash: String
  • Endpoint: https://litterbox.catbox.moe/resources/internals/api.php
  • Form fields: reqtype=fileupload, time=<expire_time>, fileToUpload=<bytes>, userhash=<hash> (if non-empty)

perform() (private):

  • Creates Easy2<ResponseBody> curl handle
  • Sets URL, enables POST, attaches multipart form
  • Appends User-Agent header via List
  • Sets forbid_reuse(true) (no connection pooling)
  • Returns response body as UTF-8 string

src/settings.rs — Configuration

Settings struct — deserialized from YAML, all fields have serde defaults:

pub struct Settings {
    pub enable_litterbox: bool,          // default: false
    pub litterbox_expire_time: ExpireTime, // default: ExpireTime::TWENTYFOUR
    pub enable_encode: bool,             // default: false
    pub encode_format: EncodeFormat,     // default: EncodeFormat::JPG
    pub encode_format_quality: u8,       // default: 80
    pub enable_resize: bool,             // default: false
    pub resize_max_resolution: [u32; 2], // default: [1024, 1024]
    pub user_agent: String,              // default: Firefox 133 UA
    pub user_hash: String,               // default: ""
    pub unexpected: HashMap<String, Value>, // unknown keys, silently collected
}

EncodeFormat enum:

Variant Serde/strum serialization Notes
JPG "JPG", "JPEG" (case-insensitive) Lossy; quality controlled by encode_format_quality
PNG "PNG" (case-insensitive) Lossless
WEBP "WEBP" (case-insensitive) Always lossless (WebPEncoder::new_lossless)

ExpireTime enum:

Variant Serde key strum serialization
ONE "1h" "1h"
TWELVE "12h" "12h"
TWENTYFOUR "24h" "24h" (default)
SEVENTYTWO "72h" "72h"

Note

ExpireTime uses explicit #[serde(rename = "Nh")] attributes. The serde deserialization is case-sensitive for these values, unlike EncodeFormat which uses ascii_case_insensitive.

Code Patterns & Conventions

  • Double fallback for settings: YAML parse failures silently fall back to Settings::new() rather than hard-failing. This means a corrupt settings.yml goes unnoticed until a validation error fires.
  • In-memory pipeline: all image operations use Cursor<Vec<u8>> — no temp files written to disk.
  • Single stdin line: io::stdin().lock().lines().next().unwrap().unwrap() — panics if stdin is closed before a line is received. The binary is designed for programmatic invocation, not interactive use.
  • Trait object dispatch: Box<dyn Consumer> is chosen at runtime based on enable_litterbox, keeping main() agnostic of upload target.
  • Exit code 4 collision: both file-not-found and UserHashError exit with code 4. This is a current design limitation.

Testing Strategy

Tests live in #[cfg(test)] modules within each source file:

Test File What it tests
test_consumers consumer.rs Live upload of 1×1 PNG to Catbox and Litterbox (real network)
test_perform consumer.rs curl success/failure (hits example.com and localhost)
test_encode_format settings.rs EncodeFormat parse, display, equality
test_from_str settings.rs YAML deserialization of empty and invalid YAML
test_validation settings.rs Full valid settings round-trip
test_encode_format_quality_validation settings.rs Quality bounds (0, 100, 110)
test_resize_max_resolution settings.rs Zero and non-power-of-two detection
test_error_displays settings.rs Error message strings

All tests require internet access. No mocking layer exists.

Potential Improvements

  • Exit code 4 collision: separate file-not-found (currently also 4) from UserHashError (4) to allow callers to distinguish them.
  • Stdin validation: the binary panics on closed stdin. A graceful error with a non-zero exit code would be more robust.
  • Settings parse failure visibility: a corrupt settings.yml falls back silently to defaults. Emitting a warning to stderr on parse failure would aid debugging.
  • Retry logic: upload failures (exit 6) could benefit from a configurable retry count, given that catbox.moe can be transiently unavailable.
  • PNG quality note: encode_format_quality silently has no effect on PNG and WebP. A warning when encode_format_quality is set alongside a lossless format would prevent user confusion.
  • User hash validation note: the source includes a comment noting the 25-character constraint "needs to be verified" — this should be confirmed against catbox.moe's actual account hash length.

Contributing

Build:

cargo build

Test (requires internet):

cargo test

Lint:

cargo clippy

Format:

cargo fmt

Release build (stripped, LTO):

cargo build --release