Files
soulseek-rs-modded/soulseek-rs-lib
Michel de Graaf f1b667f7f9 refactor: cut what a whole-repo audit proved dead, padded, or hand-rolled
Seven region audits, every finding adversarially verified against the
whole workspace before being applied; 80 landed and the two that failed
in application were refuted honestly (ParseError has a from_str_radix
producer the audit missed; NatPmpError's Display is the only reader of
its diagnostic payloads).

The bigger cuts:

- The NewPeer/PierceFireWall cluster: an operation variant nothing ever
  sent, its 77-line arm, a protocol-wrong Client::pierce_firewall that
  wrote a peer-init frame down the server socket, and the struct only
  they used. The live paths in peer/listen.rs are untouched.
- The ops loop reads `for operation in reader` instead of hand-rolling
  recv/break, de-indenting the whole dispatch body.
- Dead lib surface: Message::print_hex and four unused accessors,
  UploadFailed, registry and actor getters, build_watch_user,
  strip_ansi_codes, delegating shims on ClientContext. Removing public
  items makes this a semver major, which the release pipeline enforces.
- Never-used flexibility: the state-migration framework that never held
  a migration, border-type helpers that all answered Rounded, the TUI's
  max_concurrent_downloads field nothing read.
- Convergence: six poll loops into poll_until, three wrap-around
  navigation blocks into cycle, hit tests into Rect::contains, char
  walkers into floor/ceil_char_boundary, five Download literals into
  one helper, the suites' shared scaffolding into tests/common.

Deliberately not taken: swapping the hand-rolled zlib inflate and MD5
for crates (they replace wire-facing machinery whose tolerance of
real-world peer quirks no local test exercises) and pulling chrono into
the lib for a log timestamp. Recorded for their own decision.

Net -1002 lines. Both e2e modes stay green: 94 CLI tests each way, 16
protocol, 33 lib, unit suites, clippy pedantic clean, aislop 100/100
with the four scanner false positives suppressed inline where they
fire.
2026-07-28 12:52:18 +02: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 or bots.

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(())
}

ServerMessage and ClientOperation are #[non_exhaustive]: every protocol message the client learns to handle adds a variant, so match them with a wildcard arm.