Files
soulseek-rs-modded/soulseek-rs-lib
Michel de Graaf 435ff382d8 fix(distributed): a server with no parent figures leaves our cap alone
The speed-derived child limit read a missing ratio as a refusal, so on any
server that never announces ParentSpeedRatio — soulfind among them — the
client carried no children at all, however it was configured. A ratio we
were never told is not a decision: the server's figures set the limit only
when it has given them, and otherwise the configured cap stands, which is
how slskd runs throughout. A speed below the announced minimum still means
none, which is the safety property the rule exists for.

Also stops repeating AcceptChildren: it is a standing state on the server,
so it goes out when the answer changes rather than on every message that
happens to prompt a recount.
2026-09-09 17:23:12 +00:00
..

soulseek-rs-lib

A Rust library for implementing the Soulseek peer-to-peer protocol.

Website: https://re-invention.nl/soulseek-rs/

About

This library provides the core functionality for interacting with the Soulseek network. It can be used to build custom Soulseek clients.

Usage

Add this to your Cargo.toml:

[dependencies]
soulseek-rs-lib = "8"

Example

Simple Usage

use soulseek_rs::Client;
use std::time::Duration;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create and connect to Soulseek server
    let mut client = Client::new("username", "password");

    client.connect()?;
    client.login()?;

    // Search for files
    let results = client.search("Alex Kassian lifestream", Duration::from_secs(10))?;

    // Download first available file
    if let Some(result) = results.iter().find(|r| !r.files.is_empty()) {
        let file = &result.files[0];
        let (_download, status) = client.download(
            file.name.clone(),
            file.username.clone(),
            file.size,
            "~/Downloads".to_string(),
        )?;

        // `download` returns as soon as the request is queued: the transfer is
        // finished when the channel says so, not when this call returns.
        for update in status {
            println!("{}: {update:?}", file.name);
        }
    }

    Ok(())
}

Advanced Configuration

use soulseek_rs::{Client, ClientSettings, PeerAddress};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create client with custom settings
    let settings = ClientSettings {
        server_address: PeerAddress::new("server.slsknet.org".to_string(), 2416),
        enable_listen: true,
        listen_port: 3000,
        ..ClientSettings::new("username", "password")
    };

    let mut client = Client::with_settings(settings);
    client.connect()?;
    client.login()?;

    Ok(())
}

Asking about a peer

The server-backed lookups are request/poll pairs: you ask, then read the answer once it lands. request_user_info clears any previous answer first, so the loop below observes this request rather than the last one.

use soulseek_rs::Client;
use std::thread::sleep;
use std::time::{Duration, Instant};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut client = Client::new("username", "password");
    client.connect()?;
    client.login()?;

    client.request_user_info("someuser")?;

    let deadline = Instant::now() + Duration::from_secs(10);
    while Instant::now() < deadline {
        if let Some(info) = client.user_info("someuser") {
            // Presence and stats are separate replies, so each is an Option:
            // a reply that never arrived is None, not "offline" or "shares nothing".
            println!("{:?} {:?}", info.presence, info.stats);
            break;
        }
        sleep(Duration::from_millis(100));
    }

    // Room membership is kept current from join and leave events.
    println!("{:?}", client.room_members("lobby"));

    Ok(())
}

ClientOperation is #[non_exhaustive]: every protocol message the client learns to handle adds a variant, so match it with a wildcard arm. SharedFile is too, so build it with the .. syntax rather than listing every field.