Expand description

A library for interacting with the VTube Studio API.

This crate exposes a Client for making requests to the VTube Studio websocket API, and handles the work of mapping requests to responses (using tokio_tower::multiplex).

The client wraps a set of configurable [tower::Service] middleware for handling the authentication flow, retries, and reconnects, and uses tokio_tungstenite as the underlying websocket transport by default.

Basic usage

The example below creates a Client using the provided builder, which:

  • connects to ws://localhost:8001 using tokio_tungstenite
  • authenticates with an existing token (if present and valid)
  • reconnects when disconnected, and retries the failed request on reconnection success
  • requests a new auth token on receiving an auth error, and retries the initial failed request on authentication success
use vtubestudio::data::StatisticsRequest;
use vtubestudio::{Client, ClientEvent, Error};

#[tokio::main]
async fn main() -> Result<(), Error> {
    // An auth token from a previous successful authentication request
    let stored_token = Some("...".to_string());

    let (mut client, mut events) = Client::builder()
        .auth_token(stored_token)
        .authentication("Plugin name", "Developer name", None)
        .build_tungstenite();

    tokio::spawn(async move {
        while let Some(event) = events.next().await {
            match event {
                ClientEvent::NewAuthToken(new_token) => {
                    // This returns whenever the authentication middleware receives a new auth
                    // token. We can handle it by saving it somewhere, etc.
                    println!("Got new auth token: {new_token}");
                }
                _ => {
                    // Other events, such as connections/disconnections, API events, etc
                    println!("Got event: {:?}", event);
                }
            }
        }
    });

    // Use the client to send a `StatisticsRequest`, handling authentication if necessary.
    // The return type is inferred from the input type to be `StatisticsResponse`.
    let resp = client.send(&StatisticsRequest {}).await?;
    println!("VTube Studio has been running for {}ms", resp.uptime);

    Ok(())
}

To send multiple outgoing requests at the same time without waiting for a request to come back, you can clone the Client per request (by default, the client wraps a tower::buffer::Buffer which adds an mpsc buffer in front of the underlying websocket transport).

For an example of constructing a Client manually without the builder, check the no_middleware example in the repo.

Events

The ClientEventStream returned from the ClientBuilder will also return Events if we subscribe to them.

The example below demonstrates subscribing to TestEvents, which will be emitted every second.

use vtubestudio::data::{Event, EventSubscriptionRequest, TestEventConfig};
use vtubestudio::{Client, ClientEvent, Error};

#[tokio::main]
async fn main() -> Result<(), Error> {
    // An auth token from a previous successful authentication request
    let stored_token = Some("...".to_string());

    let (mut client, mut events) = Client::builder()
        .auth_token(stored_token)
        .authentication("Plugin name", "Developer name", None)
        .build_tungstenite();

    println!("Please accept the permission pop-up in VTube Studio");

    // Create the event subscription request, to be sent later.
    let req = EventSubscriptionRequest::subscribe(&TestEventConfig {
        test_message_for_event: "Hello from vtubestudio-rs!".to_owned(),
    })?;

    while let Some(client_event) = events.next().await {
        match client_event {
            // We receive a `Disconnected` client event whenever we are disconnected, including on
            // startup. This can be used as a cue to refresh any event subscriptions.
            ClientEvent::Disconnected => {
                println!("Connecting...");

                // Try to subscribe to test events, retrying on failure. Note that the client
                // attempts to reconnect automatically when sending a request.
                while let Err(e) = client.send(&req).await {
                    eprintln!("Failed to subscribe to test events: {e}");
                    eprintln!("Retrying in 2s...");
                    tokio::time::sleep(std::time::Duration::from_secs(2)).await;
                }
            }

            ClientEvent::Api(Event::Test(event)) => {
                assert_eq!(event.your_test_message, "Hello from vtubestudio-rs!");
                println!(
                    "VTube Studio has been running for {} seconds.",
                    event.counter
                );
            }

            other => {
                println!("Received event: {:?}", other)
            }
        }
    }

    Ok(())
}

Project structure

While the provided ClientBuilder should be sufficient for most users, each of these layers can be modified to add custom behavior if needed. E.g.,

  • using a different combination of tower middleware
  • using a different websocket library
  • adding custom request/response types
    • as an escape hatch, if new request types or fields are added to the API and you don’t feel like waiting for them to be added to this library

Optional features

By default, the tokio-tungstenite feature is enabled, which includes helpers related to the tokio_tungstenite websocket library. This can be disabled in your Cargo.toml with default-features = false:

[dependencies]
vtubestudio = { version = "0.8.0", default-features = false }

Re-exports

pub use crate::client::Client;
pub use crate::client::ClientBuilder;
pub use crate::client::ClientEvent;
pub use crate::client::ClientEventStream;
pub use crate::error::Error;
pub use crate::error::ErrorKind;
pub use crate::error::Result;

Modules

Utilities for creating Clients.
Codecs for converting to/from websocket message types.
Request/response types for the VTube Studio API.
Types related to error handling.
Service middleware used by Client.
Transport (Sink/Stream) types.