audora-inkitt-py (0.0.1)

Published 2026-08-28 16:06:39 +02:00 by aaron

Installation

pip install --index-url  audora-inkitt-py

About this package

Async Inkitt API client implementing the audora-provider-base contract

audora-inkitt-py

An async Inkitt API client for Python 3.12+, implementing the BaseProvider contract from audora-provider-base.

Because it implements a shared contract, code written against it ports to any other provider in the family with no changes beyond the constructor.

Unofficial. Inkitt publishes no API; this client is built from community reverse-engineering plus live probing, and endpoints can change without notice. Where live behaviour contradicts the documentation, live wins — every such deviation is recorded on the method that encodes it.

Install

Both this package and audora-provider-base live in a Forgejo registry rather than on PyPI, so the index has to be declared. With uv, add it to your project's pyproject.toml first:

[[tool.uv.index]]
name = "forgejo"
url = "https://forge.towu.dev/api/packages/aaron/pypi/simple"
uv add audora-inkitt-py

# pip takes the index on the command line instead
pip install --extra-index-url https://forge.towu.dev/api/packages/aaron/pypi/simple \
    audora-inkitt-py

Usage

The whole reader surface works anonymously — story detail, chapters with full text, search, comments, reviews, reading lists:

import asyncio

from audora_inkitt import InkittProvider


async def main() -> None:
    async with InkittProvider() as inkitt:
        story = await inkitt.get_story(1729326)
        await inkitt.load_chapters(story, with_text=True)
        print(story.title, "—", len(story.chapters or []), "chapters")

        async for hit in inkitt.search_stories("dragons"):
            print(hit.title)
            break


asyncio.run(main())

Authenticated use unlocks the account surface — recommendations, notifications, the reading-list write lifecycle, and authoring:

async with InkittProvider() as inkitt:
    await inkitt.authenticate("username-or-email", "password")
    lists = await inkitt.list_collections(inkitt.account_user_id()).all()

Sessions round-trip through export_session() / restore_session(). Inkitt's tokens are stable — a fresh login returns the same token rather than invalidating older ones.

Notes specific to Inkitt

  • Anonymous chapter text is a preview. Without a session, load_chapters(..., with_text=True) carries text for only the first few chapters while still claiming access; any logged-in account (no purchase needed) receives the whole book. Check story.chapters_loaded rather than trusting the quota.
  • Ids are numeric. Usernames cannot address the user routes — resolve them through search_users first. Chapters additionally need story context: pass a Chapter parsed from its story, the story= hint, or spell both halves as "storyId:chapterId".
  • Rate limiting. Nothing is documented, but a limiter exists — requests are throttled to 2/s by default, and sustained multi-hundred-page walks can still meet 429s.
  • Caching. Pass cache= at your own policy; the package imposes none. A cache shared across two accounts can leak private reads between them.
  • Paragraph comments serve counts only. The platform never serves inline-comment bodies to readers, so the canonical PARAGRAPH_COMMENTS capability is not declared; get_paragraph_comment_counts exposes what exists.
  • UNSET vs None (base vocabulary): UNSET means the projection never carried the field; None means the platform explicitly said null.

Development

uv sync --extra dev
uv run ruff format --check . && uv run ruff check .
uv run mypy src/ tests/
uv run pytest
AUDORA_PROVIDER_BASE_STRICT=1 uv run pytest

The quality-gate CI runs the same chain on every push. See AGENTS.md for the repository conventions, the architecture, and the platform invariants.

Testing

Offline tests need no network and no credentials — pytest -m "not live" (and the plain default run) stays green on a fresh clone.

Live tests run against a real Inkitt account and are skipped when unconfigured. Use a throwaway account: with writes enabled the suite creates and deletes real content, and wall posts cannot be deleted at all.

Variable Purpose
INKITT_USERNAME, INKITT_PASSWORD Account credentials (the login route accepts a username in the email field). Absent → all live tests skip. The pair also feeds the authenticate round-trip, and the wrong-password probe reuses the real INKITT_USERNAMEnever an invented address: the login route is login-or-SIGNUP and would create the account.
INKITT_TOKEN Use an existing session token; wins over the password and skips the login request. Pair with INKITT_USER_ID (and optionally INKITT_USERNAME) so the session knows its identity.
INKITT_USER_ID The account's own numeric id — user_ref/own_user_ref, and the wall-lifecycle target (wall posts land on the account's own wall).
INKITT_STORY_ID story_ref: any public story with several chapters; it need not be owned (the authoring tests build their own scratch story). Also the reading-position write target.
INKITT_LIST_ID collection_ref: a reading list owned by the account.
INKITT_ADDABLE_STORY_ID A story that exists but is not in INKITT_LIST_ID — the membership tests add and remove it (Inkitt rejects adding a story already in a list).
INKITT_FOLLOW_TARGET A numeric user id whose follow state nobody cares about — the round trip follows and unfollows it.
INKITT_COMMENTED_CHAPTER_ID The "storyId:chapterId" form — Inkitt cannot address a chapter by id alone. Keep the story half equal to INKITT_STORY_ID; the reading-position write pairs them.
INKITT_LIKEABLE_STORY_ID A QUIET story for the like round trip — the only read-back is the like count, and a busy story's moving count flakes.
INKITT_SEARCH_QUERY A query returning multiple pages. Default romance.
INKITT_ALLOW_WRITES Set to 1 to enable every mutating test — the second gate. Holding credentials never by itself authorises writes.
uv run pytest -m "not live"                            # offline only
INKITT_USERNAME=... INKITT_PASSWORD=... uv run pytest  # + live reads
INKITT_ALLOW_WRITES=1 ... uv run pytest                # + real writes
uv run pytest -rs                                      # audit what skipped

Account setup the contract suite expects:

  • A confirmed email — comment and wall writes answer 403 (AccountNotConfirmedError) otherwise.
  • The INKITT_LIST_ID list exists and is owned by the account, and INKITT_ADDABLE_STORY_ID is not in it when a run starts (the tests scrub, but a crashed run can leave state).
  • Expect the suite to leave one wall post per writes run on the account's own wall — there is no delete route.

Run pytest -rs regularly. A silently-skipped contract test is the main failure mode of a config-driven live suite.

Requirements

Requires Python: >=3.12
Details
PyPI
2026-08-28 16:06:39 +02:00
1
Proprietary
203 KiB
Assets (2)
Versions (1) View all
0.0.1 2026-08-28