Nyora (JavaScript) - v0.1.1
    Preparing search index...

    Quickstart

    Get from a clean machine to reading manga page URLs in a couple of minutes. This page is a single copy-pasteable walkthrough: install → first search → fetch a chapter → get pages, using the real exported API of nyora-sdk. It covers both the library and the nyora-cli tool — the same npm install ships both — and ends with a troubleshooting section.

    Nyora is a self-contained manga sources SDK. The default client embeds the JavaScript parser bundle inside a jsdom window, so it needs no JVM helper: HTTP is handled by Node's native fetch and HTML parsing by jsdom. The parser bundle and source catalog are kept current through over-the-air (OTA) updates.

    • Package: nyora-sdk (0.1.1)
    • Module type: ESM only ("type": "module")
    • Node.js: >=18 (developed and tested on Node 18–26)
    • License: Apache-2.0
    • Repository: https://github.com/Hasan72341/nyora-js
    • Node.js 18 or newer. The SDK relies on global fetch, AbortController, crypto.createHash, and modern jsdom — all of which need Node 18+.
    • A network connection for source requests and OTA parser-bundle updates.
    • No JVM, no desktop app, no Java. The parser engine runs in-process via jsdom.

    Check your Node version first:

    node --version   # must be >= 18
    
    npm install nyora-sdk
    

    The package is ESM-only. Import it from an .mjs file, or from a project with "type": "module" in its package.json:

    import { Nyora } from "nyora-sdk";
    

    TypeScript declarations ship in the package (./dist/index.d.ts), so types work out of the box.

    npm install -g nyora-sdk
    

    This installs two equivalent bin aliases, both pointing at dist/cli.js:

    • nyora-cli
    • nyora

    Verify it works:

    nyora-cli version
    
    nyora 0.1.1
    OTA parsers: bundled

    OTA parsers: bundled means no OTA update has been applied yet, so the SDK is using the parser bundle and catalog shipped inside the package (235 bundled sources). After an update this shows the installed OTA version number instead.

    The examples below use a real bundled source, BANANASCAN_COM. List every source available to you with nyora-cli sources (or client.sources.list()) and substitute any id you see there.

    The default export and the named export Nyora are the same class. Create a client, look up a source, run a search, and always close() when done so the embedded jsdom window is released.

    // quickstart.mjs  (or quickstart.ts)
    import { Nyora } from "nyora-sdk";

    const client = new Nyora();

    try {
    // `find` does a case-insensitive substring match on a source's id OR name.
    // It returns the FIRST match and throws if nothing matches.
    const source = client.sources.find("bananascan");
    console.log("Using source:", source.id, "—", source.name, `(${source.lang})`);

    // search(sourceId, query, page = 1) → SearchPage { entries: Manga[], hasNextPage: boolean }
    const results = await client.manga.search(source.id, "one", 1);

    console.log(`Found ${results.entries.length} entries (more: ${results.hasNextPage})`);
    for (const manga of results.entries.slice(0, 5)) {
    console.log("-", manga.title, "→", manga.url);
    }
    } finally {
    client.close(); // release the jsdom runtime
    }

    Run it:

    node quickstart.mjs
    

    If you don't have a query yet, list popular or latest manga. Both take (sourceId, page = 1) and return the same SearchPage shape:

    const popular = await client.manga.popular(source.id, 1);
    const latest = await client.manga.latest(source.id, 1);

    client.sources.find(query) lowercases query and returns the first source whose id or name contains it. If nothing matches it throws:

    No bundled source matched '<query>'
    

    To see every available source, use client.sources.list(), which returns an array of Source objects (id, name, lang, baseUrl, engine, contentType, isNsfw, …).

    Source ids are upper-snake-case, e.g. BANANASCAN_COM. You can pass either the exact id or a fuzzy fragment of the id/name to find.

    A Manga entry from a listing only carries summary fields. To get the chapter list, call details with the manga's url. Passing the known title helps some parsers resolve the entry.

    import { Nyora } from "nyora-sdk";

    const client = new Nyora();

    try {
    const source = client.sources.find("bananascan");
    const results = await client.manga.search(source.id, "one", 1);

    const first = results.entries[0];
    if (!first) throw new Error("no results — try another query or source");

    // details(sourceId, mangaUrl, options?) → MangaDetails { manga, chapters }
    const details = await client.manga.details(source.id, first.url, {
    title: first.title, // optional, passed through to the parser
    });

    console.log("Title: ", details.manga.title);
    console.log("Authors: ", details.manga.authors.join(", "));
    console.log("State: ", details.manga.state ?? "(unknown)");
    console.log("Chapters: ", details.chapters.length);

    // Each entry is a MangaChapter:
    // { id, title, number, volume, url, scanlator, uploadDate, branch, pages, index }
    const chapter = details.chapters[0];
    console.log("\nFirst chapter:", chapter.number, chapter.title, "→", chapter.url);
    } finally {
    client.close();
    }

    Key MangaChapter fields you'll use next:

    • url — the chapter URL you pass to pages.
    • branch — scanlation branch/translation name (may be null). Useful when a source has multiple translations of the same chapter.
    • uploadDate — epoch milliseconds (a number).
    • number — chapter number (may be fractional).

    Pass a chapter url to pages. It returns an ordered array of MangaPage objects, each with an image url and the request headers required to fetch it (some sources need a Referer).

    import { Nyora } from "nyora-sdk";

    const client = new Nyora();

    try {
    const source = client.sources.find("bananascan");
    const results = await client.manga.search(source.id, "one", 1);
    const details = await client.manga.details(source.id, results.entries[0].url);

    const chapter = details.chapters[0];

    // pages(sourceId, chapterUrl, options?) → MangaPage[]
    // options.branch selects a scanlation branch (default: null).
    const pages = await client.manga.pages(source.id, chapter.url, {
    branch: chapter.branch, // pass the chapter's own branch, or null
    });

    console.log(`Chapter has ${pages.length} pages:`);
    pages.forEach((page, i) => {
    console.log(`${String(i + 1).padStart(3)} ${page.url}`);
    });

    // Each MangaPage is { url: string, headers: Record<string,string> }.
    // The headers (e.g. Referer) are what you must send when downloading the image.
    } finally {
    client.close();
    }

    MangaPage.headers carries everything a source requires to serve the image. Merge them into your fetch and reuse the runtime's User-Agent, exported as BROWSER_UA:

    import { BROWSER_UA } from "nyora-sdk";

    const page = pages[0];
    const res = await fetch(page.url, {
    headers: {
    "User-Agent": BROWSER_UA,
    ...page.headers, // source-specific headers (Referer, etc.)
    },
    });
    const bytes = Buffer.from(await res.arrayBuffer());
    // write `bytes` to disk as 001.jpg, etc.

    For a ready-made archive, the CLI's download command packs a whole chapter into a .cbz for you (see below).

    Every step above maps to a nyora-cli subcommand. All of these accept -s/--source (a source id or a fuzzy name) and return exit code 0 on success, 1 on a handled error, and 2 for an unknown command.

    # 1. List / filter sources
    nyora-cli sources # all 235 sources: id<TAB>name<TAB>lang, then "(N sources)"
    nyora-cli sources --search banana # filter by id/name substring

    # 2. Search a source (-p/--page defaults to 1)
    nyora-cli search -s bananascan -p 1 one

    # 3. Fetch details + chapter list for a manga URL
    nyora-cli details -s bananascan "<manga-url>"

    # 4. Get the page image URLs for a chapter URL
    nyora-cli pages -s bananascan "<chapter-url>"
    nyora-cli pages -s bananascan --branch "English" "<chapter-url>"

    # 5. (Bonus) Download a whole chapter as a .cbz archive
    nyora-cli download -s bananascan "<chapter-url>" # → <slug>.cbz in the cwd
    nyora-cli download -s bananascan -o ~/Downloads "<chapter-url>" # -o dir → <slug>.cbz inside it
    nyora-cli download -s bananascan -o ./ch1.cbz "<chapter-url>" # -o ending in .cbz → exact file

    Notes on the CLI:

    • The <url> arguments are positional — put them after the flags. Use -- to stop flag parsing if a URL begins with -.

    • You can also browse with nyora-cli popular -s <src> and nyora-cli latest -s <src> (both take -p/--page).

    • download writes a CBZ (a plain ZIP of the page images, stored uncompressed). It prints Saved N/total pages to <file> and exits 0 if any pages were saved, 1 if none.

    • Add the global --json flag before the subcommand to emit raw JSON instead of formatted text:

      nyora-cli --json search -s bananascan one | jq '.entries[0]'
      
    • Run nyora-cli --help (or -h) for the full usage summary.

    See the CLI guide for the complete command manual.

    Running nyora-cli (or nyora) with no subcommand launches an interactive terminal reader that walks you through filter → browse/search → pick manga → pick chapter → page URLs, using arrow keys and Enter.

    nyora-cli
    

    It requires an interactive terminal (a TTY). When stdout is piped/redirected (e.g. under CI), it prints a notice and exits 0 instead of launching — so use the subcommands above for scripting. See the TUI guide.

    The parser bundle and source catalog update over the air. The client owns an OtaManager at client.ota, plus two convenience methods that also reload the runtime so new parsers take effect immediately.

    From the CLI:

    nyora-cli update          # fetch the latest parser bundle (sha256-verified) + catalog
    nyora-cli update --force # re-download even if already current
    nyora-cli version # show package + installed OTA version

    update prints either Updated to OTA version X or Already up to date (OTA version X), then the cached bundle: and sources: paths.

    From code:

    import { Nyora } from "nyora-sdk";

    const client = new Nyora();
    try {
    // Safe opportunistic check (network errors → { available: false }).
    const status = await client.checkUpdate();
    // { available: boolean, installed: number | null, latest: number | null }
    console.log(status);

    if (status.available) {
    // Downloads, SHA-256-verifies, caches, AND reloads the runtime.
    const result = await client.update(); // or client.update({ force: true })
    // { updated, version, bundlePath, sourcesPath }
    console.log("OTA now at version", result.version);
    }

    // Lower-level access if you need it:
    console.log("OTA cache dir:", client.ota.cacheDir);
    console.log("Installed OTA version:", client.ota.installedVersion()); // number | null
    } finally {
    client.close();
    }

    The OTA feed lives at https://Hasan72341.github.io/nyora-ota-parsers (exported as OTA_BASE). When the cache is empty, reads fall back to the assets bundled in the package, so the SDK works fully offline on first run. See the OTA guide for cache layout and verification details.

    nyora-sdk is ESM-only ("type": "module"). You cannot require() it from CommonJS. Fix one of:

    • Name your file .mjs, or
    • Add "type": "module" to your package.json, or
    • From CommonJS, use a dynamic import: const { Nyora } = await import("nyora-sdk");

    The package requires Node.js >= 18. It relies on a global fetch, AbortController, crypto.createHash, and modern jsdom — all Node 18+. If you see fetch is not defined, you're almost certainly on Node < 18:

    node --version   # must be >= 18 — upgrade if lower
    

    jsdom (^24.1.0) is a direct dependency and is pure JavaScript — there is no native compilation step, so a normal npm install should just work. If installation looks broken, do a clean reinstall:

    rm -rf node_modules package-lock.json
    npm install

    new Nyora() (and new ParserRuntime()) builds a jsdom window eagerly in its constructor, so a missing jsdom surfaces as Cannot find module 'jsdom' the moment you construct a client. Reinstalling dependencies resolves it.

    The cached or bundled parsers.bundle.js couldn't be evaluated into the expected global — usually a corrupted OTA cache. Clear it (the SDK then falls back to the package-bundled assets) and re-update:

    # macOS
    rm -rf ~/Library/Caches/nyora/ota
    # Linux
    rm -rf ~/.cache/nyora/ota

    nyora-cli update --force # re-download a fresh, SHA-256-verified bundle
    • No bundled source matched comes from sources.find(...): your fuzzy query matched no source id/name. List them with nyora-cli sources (or client.sources.list()).
    • Parser not found (a ParserRuntimeError) means the resolved source id has no parser in the current bundle. Try nyora-cli update to refresh parsers, or pick a different source.

    The runtime is deliberately tolerant: HTTP transport failures return an empty body instead of throwing, and the response body is returned for any status code. So a network hiccup, an anti-bot wall, or a parser that needs a different branch can yield entries: [] or pages: [] with no exception. Try:

    • Retry — transient network/anti-bot blocks are common.
    • For pages, pass the chapter's own branch (options.branch) — some sources key pages by translation branch.
    • For details, pass the known title so the parser can resolve the entry.
    • Try another source — not every source is reachable from every network.

    You ran bare nyora-cli (the TUI) in a non-interactive context (piped, redirected, or CI). That's expected — it prints a notice and exits 0. Use the subcommands (sources, search, details, pages, …) for scripting instead.

    Each Nyora / ParserRuntime / NyoraServer instance owns one jsdom window, and calls are serialized through a promise chain. For true parallelism, create multiple clients (each with its own runtime) and close() every one.

    import {
    Nyora, // default client (also the default export)
    SourcesService, // client.sources
    MangaService, // client.manga
    OtaManager, // client.ota
    ParserRuntime, // low-level jsdom parser executor
    NyoraServer, // helper-compatible REST server
    OTA_BASE, // "https://Hasan72341.github.io/nyora-ota-parsers"
    BROWSER_UA, // Chrome User-Agent string
    } from "nyora-sdk";

    const client = new Nyora();

    client.sources.list(); // Source[]
    client.sources.find("bananascan"); // Source (throws if none)

    await client.manga.popular(id, page); // SearchPage
    await client.manga.latest(id, page); // SearchPage
    await client.manga.search(id, query, page); // SearchPage
    await client.manga.details(id, url, { title }); // MangaDetails
    await client.manga.pages(id, url, { branch }); // MangaPage[]

    await client.checkUpdate(); // OtaUpdateAvailability { available, installed, latest }
    await client.update({ force }); // OtaUpdateResult { updated, version, bundlePath, sourcesPath }
    client.ota.cacheDir; // string
    client.ota.installedVersion(); // number | null

    client.close(); // release the jsdom runtime
    • Library — every service method, types, and error handling.
    • CLI — the full command manual with exit codes and recipes.
    • Server — run the helper-compatible REST API.
    • OTA — cache layout, SHA-256 verification, and offline fallback.
    • TUI — the interactive terminal reader.