feat(cli): rooms and chat subcommands
Add `rooms` (list public rooms with user counts, busiest first) and `chat <room> [message]` (join a room; say a message and exit, or listen and print incoming messages/joins/leaves for a while). Factor a shared connect_and_login helper.
This commit is contained in:
@@ -104,6 +104,22 @@ pub enum Commands {
|
|||||||
/// Username whose shares to list
|
/// Username whose shares to list
|
||||||
username: String,
|
username: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// List the public chat rooms and their user counts
|
||||||
|
Rooms,
|
||||||
|
|
||||||
|
/// Join a chat room: print messages, or send one and exit
|
||||||
|
Chat {
|
||||||
|
/// Name of the room to join
|
||||||
|
room: String,
|
||||||
|
|
||||||
|
/// Optional message to say in the room (omit to just listen)
|
||||||
|
message: Option<String>,
|
||||||
|
|
||||||
|
/// Seconds to stay and print incoming messages when only listening
|
||||||
|
#[arg(short, long, default_value = "30")]
|
||||||
|
listen_secs: u64,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_server_address(server: &str) -> color_eyre::Result<(String, u16)> {
|
pub fn parse_server_address(server: &str) -> color_eyre::Result<(String, u16)> {
|
||||||
|
|||||||
@@ -115,6 +115,31 @@ fn main() -> Result<()> {
|
|||||||
},
|
},
|
||||||
&target,
|
&target,
|
||||||
),
|
),
|
||||||
|
Some(Commands::Rooms) => list_rooms(&ClientSettings {
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
server_address: PeerAddress::new(server_host, server_port),
|
||||||
|
enable_listen: !cli.disable_listener,
|
||||||
|
listen_port: cli.listener_port,
|
||||||
|
shared_directory,
|
||||||
|
}),
|
||||||
|
Some(Commands::Chat {
|
||||||
|
room,
|
||||||
|
message,
|
||||||
|
listen_secs,
|
||||||
|
}) => chat_room(
|
||||||
|
&ClientSettings {
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
server_address: PeerAddress::new(server_host, server_port),
|
||||||
|
enable_listen: !cli.disable_listener,
|
||||||
|
listen_port: cli.listener_port,
|
||||||
|
shared_directory,
|
||||||
|
},
|
||||||
|
&room,
|
||||||
|
message.as_deref(),
|
||||||
|
listen_secs,
|
||||||
|
),
|
||||||
None => {
|
None => {
|
||||||
use ratatui::crossterm::{
|
use ratatui::crossterm::{
|
||||||
event::EnableMouseCapture,
|
event::EnableMouseCapture,
|
||||||
@@ -205,6 +230,104 @@ fn browse_user(settings: &ClientSettings, target: &str) -> Result<()> {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Connect and log in, returning the ready client or a descriptive error.
|
||||||
|
fn connect_and_login(settings: &ClientSettings) -> Result<Client> {
|
||||||
|
let mut client = Client::with_settings(settings.clone());
|
||||||
|
client
|
||||||
|
.connect()
|
||||||
|
.map_err(|e| color_eyre::eyre::eyre!("Failed to connect: {}", e))?;
|
||||||
|
if !client
|
||||||
|
.login()
|
||||||
|
.map_err(|e| color_eyre::eyre::eyre!("Failed to login: {}", e))?
|
||||||
|
{
|
||||||
|
return Err(color_eyre::eyre::eyre!("Login rejected by server"));
|
||||||
|
}
|
||||||
|
Ok(client)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn list_rooms(settings: &ClientSettings) -> Result<()> {
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
|
let client = connect_and_login(settings)?;
|
||||||
|
client
|
||||||
|
.request_room_list()
|
||||||
|
.map_err(|e| color_eyre::eyre::eyre!("Failed to list rooms: {}", e))?;
|
||||||
|
|
||||||
|
println!("📋 Fetching room list...");
|
||||||
|
let deadline = Instant::now() + Duration::from_secs(5);
|
||||||
|
let mut rooms = client.room_list();
|
||||||
|
while rooms.is_empty() && Instant::now() < deadline {
|
||||||
|
std::thread::sleep(Duration::from_millis(200));
|
||||||
|
rooms = client.room_list();
|
||||||
|
}
|
||||||
|
|
||||||
|
rooms.sort_by(|a, b| {
|
||||||
|
b.user_count
|
||||||
|
.cmp(&a.user_count)
|
||||||
|
.then_with(|| a.name.cmp(&b.name))
|
||||||
|
});
|
||||||
|
if rooms.is_empty() {
|
||||||
|
println!("(no public rooms reported)");
|
||||||
|
} else {
|
||||||
|
println!("{:>6} room", "users");
|
||||||
|
for room in rooms {
|
||||||
|
println!("{:>6} {}", room.user_count, room.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn chat_room(
|
||||||
|
settings: &ClientSettings,
|
||||||
|
room: &str,
|
||||||
|
message: Option<&str>,
|
||||||
|
listen_secs: u64,
|
||||||
|
) -> Result<()> {
|
||||||
|
use soulseek_rs::types::RoomEvent;
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
|
let client = connect_and_login(settings)?;
|
||||||
|
client
|
||||||
|
.join_room(room)
|
||||||
|
.map_err(|e| color_eyre::eyre::eyre!("Failed to join room: {}", e))?;
|
||||||
|
|
||||||
|
if let Some(text) = message {
|
||||||
|
client
|
||||||
|
.say_in_room(room, text)
|
||||||
|
.map_err(|e| color_eyre::eyre::eyre!("Failed to say: {}", e))?;
|
||||||
|
// Let the server actor flush before we drop the client.
|
||||||
|
std::thread::sleep(Duration::from_millis(500));
|
||||||
|
println!("💬 {room}: {text}");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"💬 Joined {room}. Listening for {listen_secs}s (Ctrl-C to quit)..."
|
||||||
|
);
|
||||||
|
let deadline = Instant::now() + Duration::from_secs(listen_secs);
|
||||||
|
while Instant::now() < deadline {
|
||||||
|
for event in client.take_room_events() {
|
||||||
|
match event {
|
||||||
|
RoomEvent::Message {
|
||||||
|
room: r,
|
||||||
|
username,
|
||||||
|
message,
|
||||||
|
} if r == room => println!("<{username}> {message}"),
|
||||||
|
RoomEvent::UserJoined { room: r, username } if r == room => {
|
||||||
|
println!("→ {username} joined");
|
||||||
|
}
|
||||||
|
RoomEvent::UserLeft { room: r, username } if r == room => {
|
||||||
|
println!("← {username} left");
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::thread::sleep(Duration::from_millis(200));
|
||||||
|
}
|
||||||
|
let _ = client.leave_room(room);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn send_private_message(
|
fn send_private_message(
|
||||||
settings: &ClientSettings,
|
settings: &ClientSettings,
|
||||||
recipient: &str,
|
recipient: &str,
|
||||||
|
|||||||
Reference in New Issue
Block a user