docs: add OKF knowledge base

This commit is contained in:
2026-10-19 18:41:22 +02:00
parent 86ccf2c47a
commit 44c2888a2e
6 changed files with 97 additions and 0 deletions

69
doc/structure/overview.md Normal file
View File

@@ -0,0 +1,69 @@
---
type: Project
title: webshare-api Project Overview
description: Purpose, stack, repo layout, and the verified build/test/run commands for the Webshare.cz HTTP API service.
tags: [project, node, express, torznab, qbittorrent, mcp]
timestamp: 2026-10-19T00:00:00Z
---
# webshare-api Project Overview
## Purpose
`webshare-api` re-exposes [Webshare.cz](https://webshare.cz) as the protocols *arr apps understand, plus an [MCP](https://modelcontextprotocol.io) server for AI agents. The roles:
- **Torznab indexer** — `GET /api?t=caps|search|tvsearch|movie` turns Webshare search into Torznab RSS/XML, so Sonarr/Radarr can use it as an indexer.
- **Fake torrent** — `GET /download/:ident` returns a minimal `.torrent` whose webseed points at this service's `/stream/:ident`, never at a CDN URL (Webshare links expire in ~10 minutes).
- **Stream** — `GET /stream/:ident` fetches a fresh Webshare `file_link` and issues an HTTP 302 to the CDN.
- **Fake qBittorrent client** — `/api/v2/*` is a qBittorrent-compatible download API that streams the HTTPS file to disk and reports progress; this is what *arr apps hand the torrent to.
- **Web UI** — `GET /`, a two-tab TUI-style page (Search, Queue) sharing the same download engine as the fake qBittorrent client.
- **MCP server** — `POST /mcp` (Streamable HTTP, stateless POST-only) exposes `webshare_search`, `webshare_get_link`, and `webshare_login_check` to AI agents.
Webshare credentials are **optional**: with empty `WEBSHARE_USERNAME`/`WEBSHARE_PASSWORD` the app runs in guest mode using unauthenticated `file_link` (no `wst`). Guest downloads work but use the free-tier CDN, which is typically much slower than an authenticated account.
## Stack
- **Runtime**: Node.js `>=18`, CommonJS modules.
- **Framework**: Express 4 (CORS wide open for LAN UI access; 4 MB JSON body limit).
- **Build**: [esbuild](https://esbuild.github.io) (`scripts/build.mjs`) bundles the server and the optional MCP entry each into a **single `.js` file** with dependencies inlined (no `node_modules` at runtime), and inlines the Web UI into one `dist/ui/index.html` (CSS + JS inlined).
- **Tests**: Jest 30 + supertest. `npm test` runs `jest --coverage --runInBand`. Coverage thresholds are enforced globally: **90% lines, 80% branches, 90% functions, 90% statements** over `src/**/*.js` (excluding `src/index.js`, which is the entrypoint that starts listening). Test timeout is 15 s and Jest is configured with `forceExit: true`.
- **Docker**: multi-stage build (`Dockerfile`); the final image copies only `dist/` (no source or `node_modules`), so the image is far smaller than `npm ci --omit=dev`.
- **Commit hooks**: [lefthook](https://github.com/evilmartians/lefthook) (`lefthook.yml`) runs gitleaks (secret scan), a large-file guard, and a conventional-commit subject check. **No repo-wide formatter is enforced.**
## Repo layout
```
src/ # source (CommonJS)
index.js # express app + app.listen at module load (server entry)
qbt.js # fake qBittorrent client / download queue; cancelDownload returns a boolean
mcp.js # MCP server entry (Streamable HTTP)
webshare.js # Webshare API client
torznab.js # Torznab caps/feed builders
torrent.js # minimal .torrent generation
ui/ # Web UI source (index.html, styles.css, scripts.js)
scripts/build.mjs # esbuild: server.js + mcp.js + single-file UI
dist/ # build output (gitignored; produced by npm run build or Docker)
__tests__/ # Jest + supertest tests
compose.yaml # Docker Compose
data/ # host download data dir (Compose mount target)
```
The `src/` modules: `qbt.js` is the shared download engine used by both the Web UI queue and the fake qBittorrent API; `cancelDownload(hash)` returns a boolean indicating whether a matching download was cancelled.
## Build / test / run (verified)
| Command | What it does |
|---------|--------------|
| `npm install` | Install dependencies. |
| `npm run build` | `node scripts/build.mjs` → `dist/server.js` (~1.9 MB, all deps bundled), `dist/mcp.js` (~1.8 MB), `dist/ui/index.html` (CSS+JS inlined). |
| `npm start` | `node dist/server.js` — run the built server (also serves MCP on `PORT`). |
| `npm run start:dev` | `node src/index.js` — run unbundled `src/` for local hacking (no build step). |
| `npm run mcp` / `npm run mcp:dev` | Run the MCP-only process (no Torznab/qBittorrent), serving `POST /mcp`. |
| `npm test` | `jest --coverage --runInBand` (enforces the coverage thresholds above). |
| `docker compose up -d --build` | Run via Compose (see `compose.yaml`; copy `.env.example` to `.env` first). |
Default listen port is `3001` (`PORT`). Webshare credentials are read from `WEBSHARE_USERNAME` / `WEBSHARE_PASSWORD`; full configuration reference lives in the root `README.md`. Do not commit a filled `.env`.
## Related
- Full configuration table, *arr integration steps, and MCP client config: root [`README.md`](../../README.md).