bpi-rs 0.2.0

Bilibili API client library for Rust
Documentation
# Migrating to bpi-rs 0.2

This guide tracks the current 0.2 migration surface in this repository. The
crate is moving from a broad flat API wrapper toward an idiomatic Rust SDK with
independent clients, explicit sessions, module clients, typed parameters,
payload-returning methods, and offline contract-backed tests.

The migration is intentionally breaking. Prefer updating call sites to the new
module-client style instead of wrapping the old flat method names.

## Client Construction

### 0.1 style

Older examples treated `BpiClient::new()` as a side-effecting constructor and
often assumed local account state could be loaded implicitly.

### 0.2 style

`BpiClient::new()` and `BpiClient::builder().build()` return a `BpiResult`.
Construction is explicit and does not read `account.toml` or install a global
tracing subscriber.

```rust
use bpi_rs::{BpiClient, BpiResult};

fn anonymous_client() -> BpiResult<BpiClient> {
    BpiClient::builder().build()
}
```

Use the builder when you need HTTP/session customization:

```rust
use std::time::Duration;

use bpi_rs::{BpiClient, BpiResult};

fn configured_client() -> BpiResult<BpiClient> {
    BpiClient::builder()
        .timeout(Duration::from_secs(15))
        .connect_timeout(Duration::from_secs(10))
        .user_agent("my-app/0.1")
        .referer("https://www.bilibili.com/")
        .origin("https://www.bilibili.com")
        .build()
}
```

## Session And Credentials

Credentials are explicit. Do not rely on constructor side effects.

### Cookie string

```rust
use bpi_rs::{BpiClient, BpiResult};

fn logged_in_client(cookie: &str) -> BpiResult<BpiClient> {
    BpiClient::builder().cookie(cookie).build()
}
```

### Account struct

```rust
use bpi_rs::{Account, BpiClient, BpiResult};

fn client_from_account(account: Account) -> BpiResult<BpiClient> {
    BpiClient::builder().account(account).build()
}
```

### Updating an existing client

```rust
client.set_account_from_cookie_str(
    "DedeUserID=123; SESSDATA=...; bili_jct=...; buvid3=...",
)?;
```

Use `client.clear_account()` when a client should return to guest state.

## Flat Methods To Module Clients

The preferred 0.2 API groups endpoints by domain:

```rust
let view = client.video().view(params).await?;
let nav = client.login().nav().await?;
let info = client.bangumi().info(params).await?;
```

Examples:

```rust
use bpi_rs::ids::{Bvid, MediaId};
use bpi_rs::video::VideoViewParams;
use bpi_rs::bangumi::BangumiInfoParams;

let bvid: Bvid = "BV1xx411c7mD".parse()?;
let video = client.video().view(VideoViewParams::from_bvid(bvid)).await?;

let bangumi = client
    .bangumi()
    .info(BangumiInfoParams::new(MediaId::new(28_220_978)?))
    .await?;
```

Available module clients currently include:

```text
activity, article, audio, bangumi, cheese, clientinfo, comment,
creativecenter, danmaku, dynamic, electric, fav, historytoview,
live, login, manga, message, misc, note, opus, search, user,
video, video_ranking, vip, wallet, web_widget
```

Mutating and flow-sensitive operations are still gated or intentionally kept out
of default examples.

## Response Handling

Migrated module-client methods generally return the decoded payload:

```rust
let view = client.video().view(params).await?;
println!("{}", view.title);
```

The public result alias is:

```rust
pub type BpiResult<T> = Result<T, BpiError>;
```

When a full Bilibili response envelope is required, use `ApiEnvelope<T>`.
When you are migrating a call site, prefer a module-client method that returns
`BpiResult<T>` directly. Remaining manga envelope aliases are compatibility
names over `ApiEnvelope<T>`.

If an endpoint can legitimately return success with `data: null`, its module
client method may return `BpiResult<Option<T>>`.

## Error Handling

Handle `BpiError` by category or semantic helper instead of matching only on
raw messages.

```rust
match result {
    Ok(payload) => {
        println!("ok: {payload:?}");
    }
    Err(error) if error.requires_login() => {
        eprintln!("login required");
    }
    Err(error) if error.is_risk_control() => {
        eprintln!("request was blocked by risk control");
    }
    Err(error) => return Err(error),
}
```

Useful helpers include:

```text
requires_login()
requires_vip()
is_permission_error()
is_risk_control()
semantic_error()
```

## QR Login

The crate exposes QR login primitives. Applications own QR rendering, polling
policy, timeouts, and session persistence.

```rust
use bpi_rs::login::LoginQrPollParams;

let generated = client.login().qr_generate().await?;
println!("scan url: {}", generated.url);

let status = client
    .login()
    .qr_poll(LoginQrPollParams::new(generated.qrcode_key)?)
    .await?;

if status.code == 0 {
    println!("login cookies: {:?}", status.cookies);
}
```

The promoted `login.qr.flow` contract is a Probe flow that composes
`qr_generate` and `qr_poll` with a runtime `qrcode_key`; it is not a separate
module-client method.

## Custom Requests

For endpoints not yet wrapped by a module client, use the shared request
helpers. Prefer payload-returning helpers for normal JSON envelope endpoints.

```rust
use bpi_rs::{BilibiliRequest, BpiClient, BpiResult};
use serde::Deserialize;

#[derive(Debug, Deserialize)]
struct Payload {
    value: serde_json::Value,
}

async fn custom_read(client: &BpiClient) -> BpiResult<Payload> {
    client
        .get("https://api.bilibili.com/x/example")
        .send_bpi_payload("custom.example")
        .await
}
```

Use `send_bpi_optional_payload` when an observed success response may omit or
null out the payload.

## Tests, Probe, And Local Credentials

Default development checks are intended to stay offline:

```powershell
cargo fmt --check
cargo clippy --all-targets --all-features --locked -- -D warnings
cargo check --all-features
cargo test --all-features --lib
```

Live Probe work is separate. Raw Probe output and drafts stay local under
`target/`:

```text
target/bpi-contract-drafts/...
target/bpi-probe-runs/...
target/bpi-probe-notes/...
```

Committed endpoint evidence belongs under:

```text
tests/contracts/<domain>/<endpoint>/contract.json
tests/contracts/<domain>/<endpoint>/responses/<case>.json
```

Do not commit `account.toml`, cookies, `SESSDATA`, `bili_jct`, `buvid`, raw
Probe output, or account-specific response data.

## Migration Checklist

- Replace flat calls with `client.<domain>().<method>(...)`.
- Change `BpiClient::new()` call sites to handle `BpiResult`.
- Seed credentials explicitly through the builder, `Account`, or
  `set_account_from_cookie_str`.
- Prefer typed IDs such as `Aid`, `Bvid`, `Cid`, `Mid`, `MediaId`, `SeasonId`,
  and `EpisodeId`.
- Prefer payload-returning `BpiResult<T>` methods over envelope-returning legacy
  helpers.
- Keep live and mutating behavior behind explicit opt-in controls.
- Run the offline verification gates before publishing changes.