omniplex 0.1.1

Official Rust SDK for the Omniplex API: post bot and server stats, check votes and verify webhooks.
Documentation
# omniplex

The official Rust SDK for [Omniplex](https://omniplex.gg). Post your bot's or server's stats, check whether someone has voted, and verify the webhooks Omniplex sends you.

- Async, built on [tokio]https://tokio.rs and [reqwest]https://docs.rs/reqwest with rustls, so there's no OpenSSL to install.
- Works with serenity, poise, twilight and anything else on tokio.
- Typed votes and webhook events.
- Retries rate-limited requests using the API's `Retry-After` header.

```sh
cargo add omniplex
```

Rust 1.85 or newer.

## Getting a token

Posting stats needs an API token for your bot or server. Open your [dashboard](https://omniplex.gg/dashboard), pick the bot or server, and create one under **API sessions**. Reading public data and checking votes don't need a token.

Keep the token secret, for example in an environment variable.

## Posting stats

```rust,no_run
use omniplex::{BotStats, Omniplex, PresenceStatus};

# async fn run() -> omniplex::Result<()> {
let omniplex = Omniplex::with_token(std::env::var("OMNIPLEX_TOKEN").unwrap());

omniplex
    .post_bot_stats(&BotStats {
        servers: Some(213),
        shards: Some(1),
        status: Some(PresenceStatus::Online),
        ..Default::default()
    })
    .await?;
# Ok(())
# }
```

Or let the SDK post for you. It posts once straight away, then on the interval you choose (at least a minute), in a background task:

```rust,no_run
use std::time::Duration;
use omniplex::{BotStats, Omniplex};

# async fn run() {
let omniplex = Omniplex::with_token(std::env::var("OMNIPLEX_TOKEN").unwrap());

let poster = omniplex.auto_post_bot_stats(Duration::from_secs(30 * 60), || async {
    BotStats {
        servers: Some(213),
        ..Default::default()
    }
});

// Later, if you need to:
poster.stop();
# }
```

Failed posts are printed to stderr. Use `auto_post_bot_stats_with` to handle each result yourself.

`status` is optional and reports your bot's presence (`Online`, `Idle`, `Dnd` or `Offline`). Omniplex keeps showing the last value you posted, so post it regularly if you use it.

For a server, use a server token and `post_server_stats(&ServerStats { total_members, online_members })`.

## Checking votes

```rust,no_run
# async fn run() -> omniplex::Result<()> {
let omniplex = omniplex::Omniplex::new();

if omniplex.has_voted("BOT_ID", "USER_ID").await? {
    // reward them
}

let vote = omniplex.get_bot_vote("BOT_ID", "USER_ID").await?;
if let Some(wait) = vote.wait {
    println!("They can vote again in {:?}", wait.duration());
}
# Ok(())
# }
```

`has_voted_for_server` and `get_server_vote` do the same for servers.

## Webhooks

Omniplex can call your server when someone votes for or reviews your bot or server. Set a secret on the webhook in your dashboard and choose **HMAC signature**. The SDK also verifies the older `splashtail` and `simple-auth` methods.

Always verify the request using the **raw** body. Omniplex occasionally sends one signed with the wrong secret to check that your endpoint rejects it, so return `401` when verification fails.

`verify_webhook` takes the body, the request's headers (an `http::HeaderMap`, a `HashMap<String, String>` or a slice of pairs) and your secret. With axum:

```rust,ignore
use axum::{body::Bytes, http::{HeaderMap, StatusCode}};
use omniplex::{WebhookEventData, verify_webhook};

async fn omniplex_webhook(headers: HeaderMap, body: Bytes) -> StatusCode {
    let Ok(event) = verify_webhook(&body, &headers, SECRET) else {
        return StatusCode::UNAUTHORIZED;
    };

    if let WebhookEventData::NewVote(vote) = &event.data {
        if !vote.downvote && !vote.auto {
            reward_voter(&event.creator.as_ref().unwrap().id).await;
        }
    }
    StatusCode::NO_CONTENT
}
```

### Events

| `event.data` | Fields |
|---|---|
| `NewVote` | `votes`, `per_user` (2 on a double-vote weekend), `downvote`, `auto` (cast by Auto-vote) |
| `NewReview` | `review_id`, `content`, `stars`, `owner_review` |
| `EditReview` | `review_id`, `stars` and `content` as `Changeset { old, new }`, `owner_review` |
| `DeleteReview` | `review_id`, `content`, `stars`, `owner_review` |
| `TeamEdit` | `name`, `short`, `tags`, `extra_links` and `nsfw` as `Changeset { old, new }` |
| `Unknown` | an event this version doesn't know yet, with its raw JSON |

Every event also has `creator` (who did it), `targets` (the `bot`, `server` or `team`) and `metadata` (`created_at` in seconds, and `test` for test sends).

## Errors

Failed requests return `Error::Api` with the HTTP `status`, the API's `message`, any validation `context` and `retry_after` when you're rate limited. `Error::MissingToken` means you tried to post stats without a token.

## Options

```rust,no_run
let omniplex = omniplex::Omniplex::builder()
    .token("TOKEN")
    .base_url("https://api.omniplex.gg") // the default
    .max_retries(2)                      // retries for rate limits and failed GETs
    .build();
```

Pass your own `reqwest::Client` with `.http_client(...)` to control timeouts or proxies.

## Links

- [Developer docs]https://omniplex.gg/kb/developers
- [Other SDKs]https://omniplex.gg/kb/developers/sdks
- [Report a bug]https://github.com/PlexiOSS/Omniplex/issues