Compare commits
6 Commits
c984e84cb3
...
e5ecc80630
Author | SHA1 | Date |
---|---|---|
Jez Cope | e5ecc80630 | |
Jez Cope | 218eef21ee | |
Jez Cope | 8328eb7895 | |
Jez Cope | 9b0cc516dc | |
Jez Cope | a02c9aeaa1 | |
Jez Cope | 654a33c686 |
File diff suppressed because it is too large
Load Diff
|
@ -8,15 +8,15 @@ TODO: policy on maintenance and contributions
|
|||
|
||||
## TODO
|
||||
|
||||
- [ ] Login and store session info
|
||||
- [x] Login and store session info
|
||||
- [ ] Log out
|
||||
- [ ] Show login status
|
||||
- [x] Show login status
|
||||
- [ ] Tombstone room
|
||||
- [ ] Add room alias
|
||||
- [ ] Remove room alias
|
||||
- [ ] List rooms
|
||||
- [x] List rooms
|
||||
- [ ] Filter room list in various ways (esp. Spaces!)
|
||||
- [ ] Handle multiple accounts on different homeservers
|
||||
- [ ] Upgrade room
|
||||
- [ ] Make it easier to do workflows like "invite mjolnir -> op mjolnir" in multiple rooms
|
||||
- [ ] Make it easier to script workflows like "configure ACL permissions -> invite mjolnir -> op mjolnir" in multiple rooms
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
let pkgs = nixpkgs.legacyPackages.${system};
|
||||
in {
|
||||
devShell = pkgs.mkShell {
|
||||
buildInputs = with pkgs; [ cargo rustc pkgconfig openssl.dev ];
|
||||
buildInputs = with pkgs; [ cargo rustc rustfmt pkgconfig openssl.dev ];
|
||||
};
|
||||
});
|
||||
}
|
||||
|
|
115
src/commands.rs
115
src/commands.rs
|
@ -1,15 +1,22 @@
|
|||
use matrix_sdk::{identifiers::UserId, Client, Session};
|
||||
use matrix_sdk::{
|
||||
ruma::{api::client::r0::alias, RoomAliasId, RoomId, UserId},
|
||||
Client, Session, SyncSettings,
|
||||
};
|
||||
use rpassword::prompt_password_stderr;
|
||||
use serde_lexpr;
|
||||
use std::convert::TryFrom;
|
||||
use std::fs::File;
|
||||
use std::io::{self, Write};
|
||||
|
||||
static SESSION_FILE: &str = "session_info";
|
||||
use crate::util::{build_client_config, restore_session, save_session};
|
||||
|
||||
pub async fn login(username: Option<&str>) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let stdin = io::stdin();
|
||||
type CommandResult = Result<(), Box<dyn std::error::Error>>;
|
||||
|
||||
macro_rules! room_fmt {
|
||||
() => {
|
||||
"{:30.30} | {:30.30} | {}"
|
||||
};
|
||||
}
|
||||
|
||||
pub async fn login(username: Option<&str>) -> CommandResult {
|
||||
let user_id = match username {
|
||||
Some(s) => UserId::try_from(s)?,
|
||||
None => {
|
||||
|
@ -18,6 +25,7 @@ pub async fn login(username: Option<&str>) -> Result<(), Box<dyn std::error::Err
|
|||
eprint!("Username: ");
|
||||
io::stderr().flush().unwrap();
|
||||
|
||||
let stdin = io::stdin();
|
||||
stdin.read_line(&mut s)?;
|
||||
UserId::try_from(s.trim_end())?
|
||||
}
|
||||
|
@ -25,17 +33,11 @@ pub async fn login(username: Option<&str>) -> Result<(), Box<dyn std::error::Err
|
|||
|
||||
let password = prompt_password_stderr("Password: ")?;
|
||||
|
||||
let client = Client::new_from_user_id(user_id.clone()).await?;
|
||||
println!(
|
||||
"{} logged in? {}",
|
||||
client.homeserver().await,
|
||||
client.logged_in().await
|
||||
);
|
||||
|
||||
let client =
|
||||
Client::new_from_user_id_with_config(user_id.clone(), build_client_config()).await?;
|
||||
let response = client
|
||||
.login(user_id.localpart(), &password, None, Some("mxadm"))
|
||||
.await?;
|
||||
println!("{:?}", response);
|
||||
println!(
|
||||
"{} logged in? {}",
|
||||
client.homeserver().await,
|
||||
|
@ -47,41 +49,68 @@ pub async fn login(username: Option<&str>) -> Result<(), Box<dyn std::error::Err
|
|||
user_id: response.user_id,
|
||||
device_id: response.device_id,
|
||||
};
|
||||
println!("{:?}", session_info);
|
||||
|
||||
let mut session_file = File::create(SESSION_FILE)?;
|
||||
serde_lexpr::to_writer(&mut session_file, &session_info)?;
|
||||
|
||||
save_session(session_info)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn status() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let session_file = match File::open(SESSION_FILE) {
|
||||
Ok(f) => f,
|
||||
_ => {
|
||||
println!("Not logged in: no session info file found");
|
||||
return Ok(())
|
||||
}
|
||||
};
|
||||
|
||||
let session_info: Session = serde_lexpr::from_reader(session_file)?;
|
||||
let client = Client::new_from_user_id(session_info.user_id.clone()).await?;
|
||||
if let Err(e) = client.restore_login(session_info).await {
|
||||
println!("Not logged in: unable to restore session; {}", e);
|
||||
return Ok(())
|
||||
}
|
||||
pub async fn status() -> CommandResult {
|
||||
let client = restore_session().await?;
|
||||
|
||||
// Need to make a query requiring a valid token to check session validity
|
||||
if let Ok(_) = client.devices().await {
|
||||
println!(
|
||||
"Logged in as {} ({}) with device ID {}",
|
||||
client.user_id().await.unwrap(),
|
||||
client.display_name().await?.unwrap(),
|
||||
client.device_id().await.unwrap()
|
||||
);
|
||||
} else {
|
||||
println!("Not logged in");
|
||||
match client.devices().await {
|
||||
Ok(_) => {
|
||||
println!(
|
||||
"Logged in as {} ({}) with device ID {}",
|
||||
client.user_id().await.unwrap(),
|
||||
client.display_name().await?.unwrap(),
|
||||
client.device_id().await.unwrap()
|
||||
);
|
||||
}
|
||||
Err(e) => println!("Not logged in: {:?}", e),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn list_rooms() -> CommandResult {
|
||||
let client = restore_session().await?;
|
||||
|
||||
println!("Syncing...");
|
||||
client.sync_once(SyncSettings::default()).await?;
|
||||
println!(" ...done");
|
||||
|
||||
println!("Joined rooms:");
|
||||
println!(room_fmt!(), "Name", "Main alias", "Room ID");
|
||||
println!(room_fmt!(), "----", "----------", "-------");
|
||||
for r in client.joined_rooms() {
|
||||
println!(
|
||||
room_fmt!(),
|
||||
r.display_name().await?,
|
||||
r.canonical_alias().map_or("".into(), |a| a.into_string()),
|
||||
r.room_id().as_str(),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn add_alias(room_id: &str, alias: &str) -> CommandResult {
|
||||
let room_id = RoomId::try_from(room_id)?;
|
||||
let alias_id = RoomAliasId::try_from(alias)?;
|
||||
let client = restore_session().await?;
|
||||
|
||||
let request = alias::create_alias::Request::new(&alias_id, &room_id);
|
||||
client.send(request, None).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn del_alias(alias: &str) -> CommandResult {
|
||||
let alias_id = RoomAliasId::try_from(alias)?;
|
||||
let client = restore_session().await?;
|
||||
|
||||
let request = alias::delete_alias::Request::new(&alias_id);
|
||||
client.send(request, None).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
35
src/main.rs
35
src/main.rs
|
@ -2,6 +2,7 @@
|
|||
extern crate clap;
|
||||
|
||||
mod commands;
|
||||
mod util;
|
||||
|
||||
use clap::{App, SubCommand};
|
||||
|
||||
|
@ -23,15 +24,39 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
.subcommand(SubCommand::with_name("logout").about("ends the current session"))
|
||||
.subcommand(SubCommand::with_name("status").about("displays current session status"))
|
||||
.subcommand(SubCommand::with_name("list-rooms").about("lists rooms available to the user"))
|
||||
.subcommand(
|
||||
SubCommand::with_name("add-alias")
|
||||
.about("adds an alias to a room")
|
||||
.args_from_usage(
|
||||
"<room_id> 'The ID of the room to alias'
|
||||
<alias> 'The new alias to add'",
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("del-alias")
|
||||
.about("deletes an existing alias")
|
||||
.args_from_usage("<alias> 'The alias to delete'"),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
match matches.subcommand() {
|
||||
("login", Some(login_matches)) => commands::login(login_matches.value_of("user")).await?,
|
||||
("logout", Some(_)) => println!("logging out..."),
|
||||
("login", Some(submatches)) => commands::login(submatches.value_of("user")).await?,
|
||||
("status", Some(_)) => commands::status().await?,
|
||||
("list-rooms", Some(_)) => println!("listing rooms..."),
|
||||
("", None) => println!("No subcommand given"),
|
||||
_ => unreachable!(),
|
||||
("list-rooms", Some(_)) => commands::list_rooms().await?,
|
||||
("add-alias", Some(submatches)) => {
|
||||
commands::add_alias(
|
||||
submatches.value_of("room_id").unwrap(),
|
||||
submatches.value_of("alias").unwrap(),
|
||||
)
|
||||
.await?
|
||||
}
|
||||
("del-alias", Some(submatches)) => {
|
||||
commands::del_alias(submatches.value_of("alias").unwrap()).await?
|
||||
}
|
||||
("", None) => eprintln!("No subcommand given"),
|
||||
(c, _) => {
|
||||
todo!("Subcommand '{}' not implemented yet!", c);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
use matrix_sdk::{Client, ClientConfig, Session};
|
||||
use serde_lexpr;
|
||||
use std::fs::File;
|
||||
|
||||
static SESSION_FILE: &str = "session_info";
|
||||
|
||||
pub fn build_client_config() -> ClientConfig {
|
||||
ClientConfig::new().store_path("./store")
|
||||
}
|
||||
|
||||
pub fn save_session(session: Session) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut session_file = File::create(SESSION_FILE)?;
|
||||
serde_lexpr::to_writer(&mut session_file, &session)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn restore_session() -> Result<Client, Box<dyn std::error::Error>> {
|
||||
let session_file = File::open(SESSION_FILE)?;
|
||||
let session_info: Session = serde_lexpr::from_reader(session_file)?;
|
||||
|
||||
let client =
|
||||
Client::new_from_user_id_with_config(session_info.user_id.clone(), build_client_config())
|
||||
.await?;
|
||||
client.restore_login(session_info).await?;
|
||||
Ok(client)
|
||||
}
|
Loading…
Reference in New Issue