feat(lib): chat-room protocol (list, join, leave, say) with events

Implement the Soulseek chat-room server messages against soulfind's
wire format:
- MessageFactory builders: room-list request (64), join (14),
  leave (15), say (13).
- Handlers parsing the server side: RoomList (64) names+counts,
  JoinRoom (14) membership, SayChatroom (13), UserJoinedRoom (16),
  UserLeftRoom (17), LeaveRoom (15).
- Surface everything to the client as a drained RoomEvent stream plus
  a room-list snapshot; new Client API: request_room_list, join_room,
  leave_room, say_in_room, room_list, take_room_events.

Unit tests cover each builder and handler; server-gated e2e tests
exercise message delivery and room listing against soulfind.
This commit is contained in:
michel
2026-07-19 19:47:58 +02:00
parent ae28f4236a
commit 7bc0ee2c3d
12 changed files with 718 additions and 4 deletions
+30
View File
@@ -163,6 +163,36 @@ pub enum DownloadStatus {
Failed(Option<String>),
TimedOut,
}
/// A public chat room advertised by the server (`RoomList`, code 64).
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RoomInfo {
pub name: String,
pub user_count: u32,
}
/// Something that happened in the chat-room subsystem, surfaced to the client
/// so a UI can react to it. Drained via `Client::take_room_events`.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RoomEvent {
/// The full list of public rooms (supersedes any previous snapshot).
List(Vec<RoomInfo>),
/// We successfully joined `room`; carries the current member list.
Joined { room: String, users: Vec<String> },
/// We left `room`.
Left { room: String },
/// `username` said `message` in `room`.
Message {
room: String,
username: String,
message: String,
},
/// `username` joined `room`.
UserJoined { room: String, username: String },
/// `username` left `room`.
UserLeft { room: String, username: String },
}
impl Transfer {
pub fn new_from_message(message: &mut Message) -> Self {
let direction = message.read_int32();