mirror of
https://github.com/youwen5/iamb.git
synced 2025-06-20 05:39:52 -07:00
Support sending and accepting room invitations (#7)
This commit is contained in:
parent
b6f4b03c12
commit
54ce042384
8 changed files with 279 additions and 32 deletions
|
@ -72,6 +72,9 @@ pub enum SetRoomField {
|
|||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum RoomAction {
|
||||
InviteAccept,
|
||||
InviteReject,
|
||||
InviteSend(OwnedUserId),
|
||||
Members(Box<CommandContext<ProgramContext>>),
|
||||
Set(SetRoomField),
|
||||
}
|
||||
|
@ -180,7 +183,7 @@ pub type IambResult<T> = UIResult<T, IambInfo>;
|
|||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum IambError {
|
||||
#[error("Unknown room identifier: {0}")]
|
||||
#[error("Invalid user identifier: {0}")]
|
||||
InvalidUserId(String),
|
||||
|
||||
#[error("Invalid verification user/device pair: {0}")]
|
||||
|
@ -213,6 +216,9 @@ pub enum IambError {
|
|||
#[error("Current window is not a room")]
|
||||
NoSelectedRoom,
|
||||
|
||||
#[error("You do not have a current invitation to this room")]
|
||||
NotInvited,
|
||||
|
||||
#[error("You need to join the room before you can do that")]
|
||||
NotJoined,
|
||||
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
use std::convert::TryFrom;
|
||||
|
||||
use matrix_sdk::ruma::OwnedUserId;
|
||||
|
||||
use modalkit::{
|
||||
editing::base::OpenTarget,
|
||||
env::vim::command::{CommandContext, CommandDescription},
|
||||
|
@ -21,6 +25,53 @@ use crate::base::{
|
|||
type ProgContext = CommandContext<ProgramContext>;
|
||||
type ProgResult = CommandResult<ProgramCommand>;
|
||||
|
||||
fn iamb_invite(desc: CommandDescription, ctx: &mut ProgContext) -> ProgResult {
|
||||
let args = desc.arg.strings()?;
|
||||
|
||||
if args.is_empty() {
|
||||
return Err(CommandError::InvalidArgument);
|
||||
}
|
||||
|
||||
let ract = match args[0].as_str() {
|
||||
"accept" => {
|
||||
if args.len() != 1 {
|
||||
return Err(CommandError::InvalidArgument);
|
||||
}
|
||||
|
||||
RoomAction::InviteAccept
|
||||
},
|
||||
"reject" => {
|
||||
if args.len() != 1 {
|
||||
return Err(CommandError::InvalidArgument);
|
||||
}
|
||||
|
||||
RoomAction::InviteReject
|
||||
},
|
||||
"send" => {
|
||||
if args.len() != 2 {
|
||||
return Err(CommandError::InvalidArgument);
|
||||
}
|
||||
|
||||
if let Ok(user) = OwnedUserId::try_from(args[1].as_str()) {
|
||||
RoomAction::InviteSend(user)
|
||||
} else {
|
||||
let msg = format!("Invalid user identifier: {}", args[1]);
|
||||
let err = CommandError::Error(msg);
|
||||
|
||||
return Err(err);
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
return Err(CommandError::InvalidArgument);
|
||||
},
|
||||
};
|
||||
|
||||
let iact = IambAction::from(ract);
|
||||
let step = CommandStep::Continue(iact.into(), ctx.context.take());
|
||||
|
||||
return Ok(step);
|
||||
}
|
||||
|
||||
fn iamb_verify(desc: CommandDescription, ctx: &mut ProgContext) -> ProgResult {
|
||||
let mut args = desc.arg.strings()?;
|
||||
|
||||
|
@ -182,6 +233,7 @@ fn iamb_download(desc: CommandDescription, ctx: &mut ProgContext) -> ProgResult
|
|||
fn add_iamb_commands(cmds: &mut ProgramCommands) {
|
||||
cmds.add_command(ProgramCommand { names: vec!["dms".into()], f: iamb_dms });
|
||||
cmds.add_command(ProgramCommand { names: vec!["download".into()], f: iamb_download });
|
||||
cmds.add_command(ProgramCommand { names: vec!["invite".into()], f: iamb_invite });
|
||||
cmds.add_command(ProgramCommand { names: vec!["join".into()], f: iamb_join });
|
||||
cmds.add_command(ProgramCommand { names: vec!["members".into()], f: iamb_members });
|
||||
cmds.add_command(ProgramCommand { names: vec!["rooms".into()], f: iamb_rooms });
|
||||
|
@ -203,6 +255,7 @@ pub fn setup_commands() -> ProgramCommands {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use matrix_sdk::ruma::user_id;
|
||||
use modalkit::editing::action::WindowAction;
|
||||
|
||||
#[test]
|
||||
|
@ -315,4 +368,41 @@ mod tests {
|
|||
let res = cmds.input_cmd("set room.topic A B C", ctx.clone());
|
||||
assert_eq!(res, Err(CommandError::InvalidArgument));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cmd_invite() {
|
||||
let mut cmds = setup_commands();
|
||||
let ctx = ProgramContext::default();
|
||||
|
||||
let res = cmds.input_cmd("invite accept", ctx.clone()).unwrap();
|
||||
let act = IambAction::Room(RoomAction::InviteAccept);
|
||||
assert_eq!(res, vec![(act.into(), ctx.clone())]);
|
||||
|
||||
let res = cmds.input_cmd("invite reject", ctx.clone()).unwrap();
|
||||
let act = IambAction::Room(RoomAction::InviteReject);
|
||||
assert_eq!(res, vec![(act.into(), ctx.clone())]);
|
||||
|
||||
let res = cmds.input_cmd("invite send @user:example.com", ctx.clone()).unwrap();
|
||||
let act =
|
||||
IambAction::Room(RoomAction::InviteSend(user_id!("@user:example.com").to_owned()));
|
||||
assert_eq!(res, vec![(act.into(), ctx.clone())]);
|
||||
|
||||
let res = cmds.input_cmd("invite", ctx.clone());
|
||||
assert_eq!(res, Err(CommandError::InvalidArgument));
|
||||
|
||||
let res = cmds.input_cmd("invite foo", ctx.clone());
|
||||
assert_eq!(res, Err(CommandError::InvalidArgument));
|
||||
|
||||
let res = cmds.input_cmd("invite accept @user:example.com", ctx.clone());
|
||||
assert_eq!(res, Err(CommandError::InvalidArgument));
|
||||
|
||||
let res = cmds.input_cmd("invite reject @user:example.com", ctx.clone());
|
||||
assert_eq!(res, Err(CommandError::InvalidArgument));
|
||||
|
||||
let res = cmds.input_cmd("invite send", ctx.clone());
|
||||
assert_eq!(res, Err(CommandError::InvalidArgument));
|
||||
|
||||
let res = cmds.input_cmd("invite @user:example.com", ctx.clone());
|
||||
assert_eq!(res, Err(CommandError::InvalidArgument));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -343,10 +343,10 @@ impl WindowOps<IambInfo> for IambWindow {
|
|||
.render(area, buf, state);
|
||||
},
|
||||
IambWindow::RoomList(state) => {
|
||||
let joined = store.application.worker.joined_rooms();
|
||||
let joined = store.application.worker.active_rooms();
|
||||
let mut items = joined
|
||||
.into_iter()
|
||||
.map(|(id, name)| RoomItem::new(id, name, store))
|
||||
.map(|(room, name)| RoomItem::new(room, name, store))
|
||||
.collect::<Vec<_>>();
|
||||
items.sort();
|
||||
|
||||
|
|
|
@ -92,6 +92,12 @@ impl ChatState {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn refresh_room(&mut self, store: &mut ProgramStore) {
|
||||
if let Some(room) = store.application.worker.client.get_room(self.id()) {
|
||||
self.room = room;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn message_command(
|
||||
&mut self,
|
||||
act: MessageAction,
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
use matrix_sdk::{room::Room as MatrixRoom, ruma::RoomId, DisplayName};
|
||||
use matrix_sdk::{
|
||||
room::{Invited, Room as MatrixRoom},
|
||||
ruma::RoomId,
|
||||
DisplayName,
|
||||
};
|
||||
|
||||
use modalkit::tui::{
|
||||
buffer::Buffer,
|
||||
layout::Rect,
|
||||
layout::{Alignment, Rect},
|
||||
style::{Modifier as StyleModifier, Style},
|
||||
text::{Span, Spans},
|
||||
widgets::StatefulWidget,
|
||||
text::{Span, Spans, Text},
|
||||
widgets::{Paragraph, StatefulWidget, Widget},
|
||||
};
|
||||
|
||||
use modalkit::{
|
||||
|
@ -93,6 +97,48 @@ impl RoomState {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn refresh_room(&mut self, store: &mut ProgramStore) {
|
||||
match self {
|
||||
RoomState::Chat(chat) => chat.refresh_room(store),
|
||||
RoomState::Space(space) => space.refresh_room(store),
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_invite(
|
||||
&self,
|
||||
invited: Invited,
|
||||
area: Rect,
|
||||
buf: &mut Buffer,
|
||||
store: &mut ProgramStore,
|
||||
) {
|
||||
let inviter = store.application.worker.get_inviter(invited.clone());
|
||||
|
||||
let name = match invited.canonical_alias() {
|
||||
Some(alias) => alias.to_string(),
|
||||
None => format!("{:?}", store.application.get_room_title(self.id())),
|
||||
};
|
||||
|
||||
let mut invited = vec![Span::from(format!(
|
||||
"You have been invited to join {}",
|
||||
name
|
||||
))];
|
||||
|
||||
if let Ok(Some(inviter)) = &inviter {
|
||||
invited.push(Span::from(" by "));
|
||||
invited.push(store.application.settings.get_user_span(inviter.user_id()));
|
||||
}
|
||||
|
||||
let l1 = Spans(invited);
|
||||
let l2 = Spans::from(
|
||||
"You can run `:invite accept` or `:invite reject` to accept or reject this invitation.",
|
||||
);
|
||||
let text = Text { lines: vec![l1, l2] };
|
||||
|
||||
Paragraph::new(text).alignment(Alignment::Center).render(area, buf);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
pub async fn message_command(
|
||||
&mut self,
|
||||
act: MessageAction,
|
||||
|
@ -124,6 +170,33 @@ impl RoomState {
|
|||
store: &mut ProgramStore,
|
||||
) -> IambResult<Vec<(Action<IambInfo>, ProgramContext)>> {
|
||||
match act {
|
||||
RoomAction::InviteAccept => {
|
||||
if let Some(room) = store.application.worker.client.get_invited_room(self.id()) {
|
||||
room.accept_invitation().await.map_err(IambError::from)?;
|
||||
|
||||
Ok(vec![])
|
||||
} else {
|
||||
Err(IambError::NotInvited.into())
|
||||
}
|
||||
},
|
||||
RoomAction::InviteReject => {
|
||||
if let Some(room) = store.application.worker.client.get_invited_room(self.id()) {
|
||||
room.reject_invitation().await.map_err(IambError::from)?;
|
||||
|
||||
Ok(vec![])
|
||||
} else {
|
||||
Err(IambError::NotInvited.into())
|
||||
}
|
||||
},
|
||||
RoomAction::InviteSend(user) => {
|
||||
if let Some(room) = store.application.worker.client.get_joined_room(self.id()) {
|
||||
room.invite_user_by_id(user.as_ref()).await.map_err(IambError::from)?;
|
||||
|
||||
Ok(vec![])
|
||||
} else {
|
||||
Err(IambError::NotJoined.into())
|
||||
}
|
||||
},
|
||||
RoomAction::Members(mut cmd) => {
|
||||
let width = Count::Exact(30);
|
||||
let act =
|
||||
|
@ -234,6 +307,14 @@ impl TerminalCursor for RoomState {
|
|||
|
||||
impl WindowOps<IambInfo> for RoomState {
|
||||
fn draw(&mut self, area: Rect, buf: &mut Buffer, focused: bool, store: &mut ProgramStore) {
|
||||
if let MatrixRoom::Invited(_) = self.room() {
|
||||
self.refresh_room(store);
|
||||
}
|
||||
|
||||
if let MatrixRoom::Invited(invited) = self.room() {
|
||||
self.draw_invite(invited.clone(), area, buf, store);
|
||||
}
|
||||
|
||||
match self {
|
||||
RoomState::Chat(chat) => chat.draw(area, buf, focused, store),
|
||||
RoomState::Space(space) => {
|
||||
|
|
|
@ -31,6 +31,12 @@ impl SpaceState {
|
|||
SpaceState { room_id, room, list }
|
||||
}
|
||||
|
||||
pub fn refresh_room(&mut self, store: &mut ProgramStore) {
|
||||
if let Some(room) = store.application.worker.client.get_room(self.id()) {
|
||||
self.room = room;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn room(&self) -> &MatrixRoom {
|
||||
&self.room
|
||||
}
|
||||
|
@ -88,7 +94,13 @@ impl<'a> StatefulWidget for Space<'a> {
|
|||
type State = SpaceState;
|
||||
|
||||
fn render(self, area: Rect, buffer: &mut Buffer, state: &mut Self::State) {
|
||||
let members = self.store.application.worker.space_members(state.room_id.clone()).unwrap();
|
||||
let members =
|
||||
if let Ok(m) = self.store.application.worker.space_members(state.room_id.clone()) {
|
||||
m
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
let items = members
|
||||
.into_iter()
|
||||
.filter_map(|id| {
|
||||
|
|
|
@ -17,7 +17,7 @@ use matrix_sdk::{
|
|||
encryption::verification::{SasVerification, Verification},
|
||||
event_handler::Ctx,
|
||||
reqwest,
|
||||
room::{Messages, MessagesOptions, Room as MatrixRoom, RoomMember},
|
||||
room::{Invited, Messages, MessagesOptions, Room as MatrixRoom, RoomMember},
|
||||
ruma::{
|
||||
api::client::{
|
||||
room::create_room::v3::{Request as CreateRoomRequest, RoomPreset},
|
||||
|
@ -98,13 +98,14 @@ fn oneshot<T>() -> (ClientReply<T>, ClientResponse<T>) {
|
|||
}
|
||||
|
||||
pub enum WorkerTask {
|
||||
ActiveRooms(ClientReply<Vec<(MatrixRoom, DisplayName)>>),
|
||||
DirectMessages(ClientReply<Vec<(MatrixRoom, DisplayName)>>),
|
||||
Init(AsyncProgramStore, ClientReply<()>),
|
||||
LoadOlder(OwnedRoomId, Option<String>, u32, ClientReply<MessageFetchResult>),
|
||||
Login(LoginStyle, ClientReply<IambResult<EditInfo>>),
|
||||
GetInviter(Invited, ClientReply<IambResult<Option<RoomMember>>>),
|
||||
GetRoom(OwnedRoomId, ClientReply<IambResult<(MatrixRoom, DisplayName)>>),
|
||||
JoinRoom(String, ClientReply<IambResult<OwnedRoomId>>),
|
||||
JoinedRooms(ClientReply<Vec<(MatrixRoom, DisplayName)>>),
|
||||
Members(OwnedRoomId, ClientReply<IambResult<Vec<RoomMember>>>),
|
||||
SpaceMembers(OwnedRoomId, ClientReply<IambResult<Vec<OwnedRoomId>>>),
|
||||
Spaces(ClientReply<Vec<(MatrixRoom, DisplayName)>>),
|
||||
|
@ -117,6 +118,9 @@ pub enum WorkerTask {
|
|||
impl Debug for WorkerTask {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
match self {
|
||||
WorkerTask::ActiveRooms(_) => {
|
||||
f.debug_tuple("WorkerTask::ActiveRooms").field(&format_args!("_")).finish()
|
||||
},
|
||||
WorkerTask::DirectMessages(_) => {
|
||||
f.debug_tuple("WorkerTask::DirectMessages")
|
||||
.field(&format_args!("_"))
|
||||
|
@ -142,6 +146,9 @@ impl Debug for WorkerTask {
|
|||
.field(&format_args!("_"))
|
||||
.finish()
|
||||
},
|
||||
WorkerTask::GetInviter(invite, _) => {
|
||||
f.debug_tuple("WorkerTask::GetInviter").field(invite).finish()
|
||||
},
|
||||
WorkerTask::GetRoom(room_id, _) => {
|
||||
f.debug_tuple("WorkerTask::GetRoom")
|
||||
.field(room_id)
|
||||
|
@ -154,9 +161,6 @@ impl Debug for WorkerTask {
|
|||
.field(&format_args!("_"))
|
||||
.finish()
|
||||
},
|
||||
WorkerTask::JoinedRooms(_) => {
|
||||
f.debug_tuple("WorkerTask::JoinedRooms").field(&format_args!("_")).finish()
|
||||
},
|
||||
WorkerTask::Members(room_id, _) => {
|
||||
f.debug_tuple("WorkerTask::Members")
|
||||
.field(room_id)
|
||||
|
@ -245,6 +249,14 @@ impl Requester {
|
|||
return response.recv();
|
||||
}
|
||||
|
||||
pub fn get_inviter(&self, invite: Invited) -> IambResult<Option<RoomMember>> {
|
||||
let (reply, response) = oneshot();
|
||||
|
||||
self.tx.send(WorkerTask::GetInviter(invite, reply)).unwrap();
|
||||
|
||||
return response.recv();
|
||||
}
|
||||
|
||||
pub fn get_room(&self, room_id: OwnedRoomId) -> IambResult<(MatrixRoom, DisplayName)> {
|
||||
let (reply, response) = oneshot();
|
||||
|
||||
|
@ -261,10 +273,10 @@ impl Requester {
|
|||
return response.recv();
|
||||
}
|
||||
|
||||
pub fn joined_rooms(&self) -> Vec<(MatrixRoom, DisplayName)> {
|
||||
pub fn active_rooms(&self) -> Vec<(MatrixRoom, DisplayName)> {
|
||||
let (reply, response) = oneshot();
|
||||
|
||||
self.tx.send(WorkerTask::JoinedRooms(reply)).unwrap();
|
||||
self.tx.send(WorkerTask::ActiveRooms(reply)).unwrap();
|
||||
|
||||
return response.recv();
|
||||
}
|
||||
|
@ -406,13 +418,17 @@ impl ClientWorker {
|
|||
assert!(self.initialized);
|
||||
reply.send(self.join_room(room_id).await);
|
||||
},
|
||||
WorkerTask::GetInviter(invited, reply) => {
|
||||
assert!(self.initialized);
|
||||
reply.send(self.get_inviter(invited).await);
|
||||
},
|
||||
WorkerTask::GetRoom(room_id, reply) => {
|
||||
assert!(self.initialized);
|
||||
reply.send(self.get_room(room_id).await);
|
||||
},
|
||||
WorkerTask::JoinedRooms(reply) => {
|
||||
WorkerTask::ActiveRooms(reply) => {
|
||||
assert!(self.initialized);
|
||||
reply.send(self.joined_rooms().await);
|
||||
reply.send(self.active_rooms().await);
|
||||
},
|
||||
WorkerTask::LoadOlder(room_id, fetch_id, limit, reply) => {
|
||||
assert!(self.initialized);
|
||||
|
@ -716,6 +732,12 @@ impl ClientWorker {
|
|||
}
|
||||
}
|
||||
|
||||
async fn get_inviter(&mut self, invited: Invited) -> IambResult<Option<RoomMember>> {
|
||||
let details = invited.invite_details().await.map_err(IambError::from)?;
|
||||
|
||||
Ok(details.inviter)
|
||||
}
|
||||
|
||||
async fn get_room(&mut self, room_id: OwnedRoomId) -> IambResult<(MatrixRoom, DisplayName)> {
|
||||
if let Some(room) = self.client.get_room(&room_id) {
|
||||
let name = room.display_name().await.map_err(IambError::from)?;
|
||||
|
@ -749,33 +771,53 @@ impl ClientWorker {
|
|||
}
|
||||
}
|
||||
|
||||
async fn direct_messages(&mut self) -> Vec<(MatrixRoom, DisplayName)> {
|
||||
async fn direct_messages(&self) -> Vec<(MatrixRoom, DisplayName)> {
|
||||
let mut rooms = vec![];
|
||||
|
||||
for room in self.client.joined_rooms().into_iter() {
|
||||
if room.is_space() || !room.is_direct() {
|
||||
for room in self.client.invited_rooms().into_iter() {
|
||||
if !room.is_direct() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Ok(name) = room.display_name().await {
|
||||
rooms.push((MatrixRoom::from(room), name))
|
||||
let name = room.display_name().await.unwrap_or(DisplayName::Empty);
|
||||
|
||||
rooms.push((room.into(), name));
|
||||
}
|
||||
|
||||
for room in self.client.joined_rooms().into_iter() {
|
||||
if !room.is_direct() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let name = room.display_name().await.unwrap_or(DisplayName::Empty);
|
||||
|
||||
rooms.push((room.into(), name));
|
||||
}
|
||||
|
||||
return rooms;
|
||||
}
|
||||
|
||||
async fn joined_rooms(&mut self) -> Vec<(MatrixRoom, DisplayName)> {
|
||||
async fn active_rooms(&self) -> Vec<(MatrixRoom, DisplayName)> {
|
||||
let mut rooms = vec![];
|
||||
|
||||
for room in self.client.invited_rooms().into_iter() {
|
||||
if room.is_space() || room.is_direct() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let name = room.display_name().await.unwrap_or(DisplayName::Empty);
|
||||
|
||||
rooms.push((room.into(), name));
|
||||
}
|
||||
|
||||
for room in self.client.joined_rooms().into_iter() {
|
||||
if room.is_space() || room.is_direct() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Ok(name) = room.display_name().await {
|
||||
rooms.push((MatrixRoom::from(room), name))
|
||||
}
|
||||
let name = room.display_name().await.unwrap_or(DisplayName::Empty);
|
||||
|
||||
rooms.push((room.into(), name));
|
||||
}
|
||||
|
||||
return rooms;
|
||||
|
@ -857,17 +899,27 @@ impl ClientWorker {
|
|||
Ok(rooms)
|
||||
}
|
||||
|
||||
async fn spaces(&mut self) -> Vec<(MatrixRoom, DisplayName)> {
|
||||
async fn spaces(&self) -> Vec<(MatrixRoom, DisplayName)> {
|
||||
let mut spaces = vec![];
|
||||
|
||||
for room in self.client.invited_rooms().into_iter() {
|
||||
if !room.is_space() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let name = room.display_name().await.unwrap_or(DisplayName::Empty);
|
||||
|
||||
spaces.push((room.into(), name));
|
||||
}
|
||||
|
||||
for room in self.client.joined_rooms().into_iter() {
|
||||
if !room.is_space() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Ok(name) = room.display_name().await {
|
||||
spaces.push((MatrixRoom::from(room), name));
|
||||
}
|
||||
let name = room.display_name().await.unwrap_or(DisplayName::Empty);
|
||||
|
||||
spaces.push((room.into(), name));
|
||||
}
|
||||
|
||||
return spaces;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue