The Problem

Self-hosted media request tools like Jellyseerr/Overseerr force users into a web UI. For households or communities where WhatsApp is the primary communication channel, that's a barrier — requests don't get made, or they get made through a middleman. Whatseerr bridges that gap by turning WhatsApp messages into Seerr API calls.

What This Does

Whatseerr is a Node.js/Express bot that listens to WhatsApp messages through a WAHA HTTP API session, queries a Seerr (Jellyseerr/Overseerr) instance, presents numbered search results, and submits the user's selection as a media request. It also receives Seerr webhook notifications and forwards them into WhatsApp chats.

The core flow lives in lib/commands/search-command.js and lib/commands/selection-command.js — search, show numbered results, wait for a number reply, submit. lib/request.js wraps the Seerr API (search, details, create request, approve). lib/waha-client.js wraps the WAHA API for sending messages and resolving phone numbers to session IDs. lib/webhook-helpers.js (1,034 lines) formats Seerr notification payloads.

How It Is Wired

Execution starts at main in cli.js:39, which loads config (loadConfig in lib/utils.js, reading the filesystem via fs.readFileSync), then starts the Express server (createServer in lib/server.js). Incoming WhatsApp webhooks hit taskFn (lib/server.js:205), which routes through handleMessage → getPhoneNumberByLid — an outbound network call to WAHA — in as few as three hops from entry.

The command system is a registry (lib/commands/index.js) with BaseCommand subclasses: AgentCommand, ApprovalCommand, SearchCommand, SelectionCommand, SubscriptionsCommand. execute in agent-command.js is the largest reachable function (99 functions) and calls sendMessage and getErrorDetails 24 times each.

Two things matter for anyone changing this code:

  • lib/errors/error-formatter.js is a hub: 14 modules import getErrorDetails, and it's called from 29 places. Changes there ripple everywhere.
  • lib/utils.js and lib/waha-client.js participate in a circular import. Both are heavily imported (11 and 10 dependents respectively). Breaking that cycle should be a priority before any large refactor.

File map by responsibility: lib/request.js owns all Seerr API calls; lib/waha-client.js owns WhatsApp transport; lib/state/cache-state.js holds per-user search results between the search and selection steps; lib/queue/message-queue.js serializes outbound messages per user; lib/subscriptions/subscription-manager.js persists chat subscriptions to disk.

How To Use It

Docker is the supported path:

docker pull ghcr.io/sufxgit/whatseerr:latest

Configuration lives in a config.json mounted at /config. On first run a config.example.json is copied there. Required settings: jellyseerr.apiKey, waha.apiKey, host, and userIdMappings (WhatsApp phone number → Seerr user ID). Point the WAHA session webhook at http://YOUR_HOST:3006/requests, and optionally the Seerr webhook at http://YOUR_HOST:3006/seerr. Send r The Matrix from WhatsApp; reply with a number to confirm.

Code Health & Issues

Static analysis found 22 findings (3 high, 19 medium) across 6 kinds. The high-severity items: a circular import between lib/utils.js and lib/waha-client.js, and 28 duplicated 6-line code blocks across cli.js, lib/request.js, lib/commands/search-command.js, and lib/commands/selection-command.js. Medium findings include high branching density in lib/request.js (249 branch points over 575 lines), the error-formatter.js hub module, and deep nesting in lib/season-utils.js (depth 6).

The code health audit adds: High — GitHub Actions pinned to mutable tags (docker/setup-buildx-action@v3, etc.) instead of commit SHAs; High — no test suite at all (37 source files, zero tests); Medium — no Dependabot/Renovate, unpinned node:20-alpine base image, no dependency vulnerability scan in CI, no non-root USER in the Dockerfile; Low — no timeout-minutes on workflow jobs.

The Bottom Line

The architecture is sound for a bot of this scope: clean command separation, a queue for outbound messaging, and sensible state management. The absence of tests and the circular dependency are real liabilities — any refactor of the messaging or request paths will be done blind. Suitable for a small community or household that lives in WhatsApp and runs Jellyseerr; not something to build a large multi-tenant deployment on without addressing the test gap first.