presenceforge

Crates.iopresenceforge
lib.rspresenceforge
version0.1.0
created_at2025-10-19 16:41:20.895312+00
updated_at2025-10-24 17:42:25.457395+00
descriptionA library for Discord Rich Presence (IPC) integration
homepage
repositoryhttps://github.com/Sreehari425/presenceforge
max_upload_size
id1890606
size441,770
SreehariAnil255 (Sreehari425)

documentation

README

PresenceForge

A Rust library for Discord Rich Presence that actually works without the headaches. No more fighting with the Discord SDK or dealing with complex C bindings.

License Rust Crates.io Version

Note: This is currently in development (v0.1.0). Things might break. This is a learning/hobby project. Features and APIs may change in future versions.

Documentation

Want to build your own RPC client?

  • Discord RPC from Scratch - this is What I found while building this library. I could be wrong about some things, so feel free to correct me!

What Works

  • Linux and macOS (Unix domain sockets)
  • Windows support (named pipes)
  • Flatpak Discord support (automatic detection)
  • Basic Rich Presence activities
  • Activity builder pattern
  • Images, buttons, and timestamps
  • Async support with runtime-agnostic design
  • Support for tokio, async-std, and smol
  • Flexible pipe/socket selection

Quick Start

Add PresenceForge to your Cargo.toml:

[dependencies]
presenceforge = "0.1.0"

For async support, add one of the runtime features:

[dependencies]
presenceforge = { version = "0.1.0", features = ["tokio-runtime"] }
# OR
presenceforge = { version = "0.1.0", features = ["async-std-runtime"] }
# OR
presenceforge = { version = "0.1.0", features = ["smol-runtime"] }

Basic Usage (Synchronous)

use presenceforge::ActivityBuilder;
use presenceforge::sync::DiscordIpcClient;
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut client = DiscordIpcClient::new("your_client_id")?;
    client.connect()?;

    let activity = ActivityBuilder::new()
        .state("Playing a game")
        .details("In the menu")
        .start_timestamp_now()
        .large_image("game_logo")
        .large_text("My Awesome Game")
        .build();

    client.set_activity(&activity)?;

    // Keep the activity active
    std::thread::sleep(std::time::Duration::from_secs(10));

    client.clear_activity()?;
    Ok(())
}

Async Usage with Tokio

use presenceforge::{AsyncDiscordIpcClient, ActivityBuilder, Result};

#[tokio::main]
async fn main() -> Result {
    let mut client = AsyncDiscordIpcClient::new("your_client_id").await?;
    client.connect().await?;

    let activity = ActivityBuilder::new()
        .state("Playing a game")
        .details("In the menu")
        .start_timestamp_now()
        .large_image("game_logo")
        .large_text("My Awesome Game")
        .build();

    client.set_activity(&activity).await?;
    tokio::time::sleep(tokio::time::Duration::from_secs(10)).await;
    client.clear_activity().await?;

    Ok(())
}

Async Usage with async-std

use presenceforge::{AsyncDiscordIpcClient, ActivityBuilder, Result};
use async_std::task;
use std::time::Duration;

#[async_std::main]
async fn main() -> Result {
    let mut client = AsyncDiscordIpcClient::new("your_client_id").await?;
    client.connect().await?;

    let activity = ActivityBuilder::new()
        .state("Playing a game")
        .details("In the menu")
        .start_timestamp_now()
        .large_image("game_logo")
        .large_text("My Awesome Game")
        .build();

    client.set_activity(&activity).await?;
    task::sleep(Duration::from_secs(10)).await;
    client.clear_activity().await?;

    Ok(())
}

Async Usage with smol

use presenceforge::{AsyncDiscordIpcClient, ActivityBuilder, Result};
use std::time::Duration;

fn main() -> Result {
    smol::block_on(async {
        let mut client = AsyncDiscordIpcClient::new("your_client_id").await?;
        client.connect().await?;

        let activity = ActivityBuilder::new()
            .state("Playing a game")
            .details("In the menu")
            .start_timestamp_now()
            .large_image("game_logo")
            .large_text("My Awesome Game")
            .build();

        client.set_activity(&activity).await?;
        smol::Timer::after(Duration::from_secs(10)).await;
        client.clear_activity().await?;

        Ok(())
    })
}

Getting Your Discord Application ID

  1. Go to the Discord Developer Portal
  2. Create a new application or select an existing one
  3. Copy the Application ID from the General Information page
  4. (Optional) Upload images in the Rich Presence Art Assets section

Configuration Methods

All examples support three ways to provide your Discord Client ID:

1. Command-line Argument (Recommended for testing)

cargo run --example basic -- --client-id YOUR_CLIENT_ID

2. Environment Variable

DISCORD_CLIENT_ID=YOUR_CLIENT_ID cargo run --example basic

3. .env File (Recommended for development)

# Copy the example file
cp .env.example .env

# Edit .env and add your client ID
# DISCORD_CLIENT_ID=your_client_id_here

# Then run any example
cargo run --example basic

Priority Order: Command-line argument → Environment variable → .env file

Platform Support

Platform IPC Method Status
Linux Unix Domain Sockets [x]
macOS Unix Domain Sockets [x]
Windows Named Pipes [x]

API Reference

Client

  • DiscordIpcClient::new(client_id) - Create a new client
  • client.connect() - Connect to Discord
  • client.set_activity(activity) - Set Rich Presence activity
  • client.clear_activity() - Clear current activity

Activity Builder

The ActivityBuilder provides a fluent interface for creating activities:

ActivityBuilder::new()
    .state("Custom state")           // What the player is doing
    .details("Custom details")       // Additional context
    .start_timestamp_now()           // Start time (current)
    .start_timestamp(timestamp)      // Start time (custom)
    .end_timestamp(timestamp)        // End time
    .large_image("image_key")        // Large image asset
    .large_text("Hover text")        // Large image hover text
    .small_image("image_key")        // Small image asset
    .small_text("Hover text")        // Small image hover text
    .button("Label", "https://url")  // Clickable button (max 2)
    .party("id",1, 4)               // Party size (current, max)
    .build()

Running Examples

For detailed information about all available examples and configuration options, see the Examples README.

Clone the repository and run the included examples:

git clone https://github.com/Sreehari425/presenceforge.git
cd presenceforge

# Basic example (synchronous)
cargo run --example basic -- --client-id YOUR_CLIENT_ID

# Game demo with dynamic status
cargo run --example game_demo -- --client-id YOUR_CLIENT_ID

# Developer coding status
cargo run --example coding_status -- --client-id YOUR_CLIENT_ID

# Custom activity without builder
cargo run --example custom_activity -- --client-id YOUR_CLIENT_ID

# Async example with Tokio
cargo run --example async_tokio --features tokio-runtime -- --client-id YOUR_CLIENT_ID

# Async example with async-std
cargo run --example async_std --features async-std-runtime -- --client-id YOUR_CLIENT_ID

# Async example with smol
cargo run --example async_smol --features smol-runtime -- --client-id YOUR_CLIENT_ID

# Complete builder reference - Shows ALL ActivityBuilder options
cargo run --example builder_all -- --client-id YOUR_CLIENT_ID

# Connection retry and error handling
cargo run --example connection_retry -- --client-id YOUR_CLIENT_ID

# Pipe selection and discovery
cargo run --example pipe_selection -- --client-id YOUR_CLIENT_ID

Or use the .env file method (recommended for development):

# Set up .env file once
cp .env.example .env
# Edit .env and add: DISCORD_CLIENT_ID=your_client_id_here

# Then run examples without specifying client ID
cargo run --example basic
cargo run --example game_demo
cargo run --example async_tokio --features tokio-runtime

Note: Replace YOUR_CLIENT_ID with your actual Discord application ID, or use the .env file method for convenience.

Error Handling

PresenceForge uses the Result type for error handling:

use presenceforge::DiscordIpcError;
use presenceforge::sync::DiscordIpcClient;
match client.connect() {
    Ok(_) => println!("Connected successfully!"),
    Err(DiscordIpcError::ConnectionFailed) => {
        eprintln!("Failed to connect - is Discord running?");
    }
    Err(e) => eprintln!("Error: {}", e),
}

TODO

  • Better error messages
  • Party/lobby functionality (partial implementation)
  • Async support (tokio, async-std, and smol)
  • More comprehensive examples
  • Publish to crates.io
  • CI/CD pipeline
  • Proper documentation
  • Connection retry logic with exponential backoff
  • Activity validation

License

This project is licensed under either of

at your option.

Commit count: 0

cargo fmt