use clap::{ColorChoice, CommandFactory, Parser, ValueEnum};
use colored::Colorize;
use directories::ProjectDirs;
use matrix_sdk::ruma::api::client::room::Visibility;
use regex::Regex;
use rpassword::read_password;
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
use std::env;
use std::fmt::{self, Debug};
use std::fs::{self, File};
use std::io::{self, stdin, stdout, IsTerminal, Read, Write};
use std::path::{Path, PathBuf};
use std::str::FromStr;
use thiserror::Error;
use tracing::{debug, enabled, error, info, warn, Level};
use tracing_subscriber::EnvFilter;
use update_informer::{registry, Check};
use url::Url;
use matrix_sdk::{
ruma::{
OwnedDeviceId,
OwnedMxcUri,
OwnedUserId,
},
Client,
SessionMeta,
};
mod mclient;
use crate::mclient::{
bootstrap, convert_to_full_alias_ids, convert_to_full_mxc_uris, convert_to_full_room_id,
convert_to_full_room_ids, convert_to_full_user_ids, delete_devices_pre, devices, file,
get_avatar, get_avatar_url, get_display_name, get_masterkey, get_profile, get_room_info,
invited_rooms, joined_members, joined_rooms, left_rooms, login, logout, logout_local,
media_delete, media_download, media_mxc_to_http, media_upload, message,
replace_star_with_rooms, restore_credentials, restore_login, room_ban, room_create,
room_enable_encryption, room_forget, room_get_state, room_get_visibility, room_invite,
room_join, room_kick, room_leave, room_resolve_alias, room_unban, rooms, set_avatar,
set_avatar_url, set_display_name, unset_avatar_url, verify,
};
mod listen;
use crate::listen::{listen_all, listen_forever, listen_once, listen_tail};
const VERSION_O: Option<&str> = option_env!("CARGO_PKG_VERSION");
const VERSION: &str = "unknown version";
const PKG_NAME_O: Option<&str> = option_env!("CARGO_PKG_NAME");
const PKG_NAME: &str = "matrix-commander";
const BIN_NAME_O: Option<&str> = option_env!("CARGO_BIN_NAME");
const BIN_NAME: &str = "matrix-commander-rs";
const BIN_NAME_UNDERSCORE: &str = "matrix_commander_rs";
const PKG_REPOSITORY_O: Option<&str> = option_env!("CARGO_PKG_REPOSITORY");
const PKG_REPOSITORY: &str = "https://github.com/8go/matrix-commander-rs/";
const CREDENTIALS_FILE_DEFAULT: &str = "credentials.json";
const STORE_DIR_DEFAULT: &str = "store/";
const TIMEOUT_DEFAULT: u64 = 60;
const URL_README: &str = "https://raw.githubusercontent.com/8go/matrix-commander-rs/main/README.md";
#[derive(Error, Debug)]
pub enum Error {
#[error("{0}")]
Custom(&'static str),
#[error("No valid home directory path")]
NoHomeDirectory,
#[error("Not logged in")]
NotLoggedIn,
#[error("Invalid Room")]
InvalidRoom,
#[error("Homeserver Not Set")]
HomeserverNotSet,
#[error("Invalid File")]
InvalidFile,
#[error("Login Failed")]
LoginFailed,
#[error("Verify Failed or Partially Failed")]
VerifyFailed,
#[error("Bootstrap Failed")]
BootstrapFailed,
#[error("Login Unnecessary")]
LoginUnnecessary,
#[error("Send Failed")]
SendFailed,
#[error("Listen Failed")]
ListenFailed,
#[error("Create Room Failed")]
CreateRoomFailed,
#[error("Leave Room Failed")]
LeaveRoomFailed,
#[error("Forget Room Failed")]
ForgetRoomFailed,
#[error("Invite Room Failed")]
InviteRoomFailed,
#[error("Join Room Failed")]
JoinRoomFailed,
#[error("Ban Room Failed")]
BanRoomFailed,
#[error("Unban Room Failed")]
UnbanRoomFailed,
#[error("Kick Room Failed")]
KickRoomFailed,
#[error("Resolve Room Alias Failed")]
ResolveRoomAliasFailed,
#[error("Enable Encryption Failed")]
EnableEncryptionFailed,
#[error("Room Get Visibility Failed")]
RoomGetVisibilityFailed,
#[error("Room Get State Failed")]
RoomGetStateFailed,
#[error("JoinedMembersFailed")]
JoinedMembersFailed,
#[error("Delete Device Failed")]
DeleteDeviceFailed,
#[error("Get Avatar Failed")]
GetAvatarFailed,
#[error("Set Avatar Failed")]
SetAvatarFailed,
#[error("Get Avatar URL Failed")]
GetAvatarUrlFailed,
#[error("Set Avatar URL Failed")]
SetAvatarUrlFailed,
#[error("Unset Avatar URL Failed")]
UnsetAvatarUrlFailed,
#[error("Get Displayname Failed")]
GetDisplaynameFailed,
#[error("Set Displayname Failed")]
SetDisplaynameFailed,
#[error("Get Profile Failed")]
GetProfileFailed,
#[error("Get Masterkey Failed")]
GetMasterkeyFailed,
#[error("Restoring Login Failed")]
RestoreLoginFailed,
#[error("Media Upload Failed")]
MediaUploadFailed,
#[error("Media Download Failed")]
MediaDownloadFailed,
#[error("Media Delete Failed")]
MediaDeleteFailed,
#[error("MXC TO HTTP Failed")]
MediaMxcToHttpFailed,
#[error("Invalid Client Connection")]
InvalidClientConnection,
#[error("Unknown CLI parameter")]
UnknownCliParameter,
#[error("Unsupported CLI parameter: {0}")]
UnsupportedCliParameter(&'static str),
#[error("Missing Room")]
MissingRoom,
#[error("Missing User")]
MissingUser,
#[error("Missing Password")]
MissingPassword,
#[error("Missing CLI parameter")]
MissingCliParameter,
#[error("Not Implemented Yet")]
NotImplementedYet,
#[error("No Credentials Found")]
NoCredentialsFound,
#[error(transparent)]
IO(#[from] std::io::Error),
#[error(transparent)]
Matrix(#[from] matrix_sdk::Error),
#[error(transparent)]
Json(#[from] serde_json::Error),
#[error(transparent)]
Http(#[from] matrix_sdk::HttpError),
}
#[allow(dead_code)]
impl Error {
pub(crate) fn custom<T>(message: &'static str) -> Result<T, Error> {
Err(Error::Custom(message))
}
}
#[derive(Clone, Debug, Copy, PartialEq, Default, ValueEnum)]
enum Login {
#[default]
None,
Password,
AccessToken,
Sso,
}
impl Login {
pub fn is_password(&self) -> bool {
self == &Self::Password
}
pub fn is_none(&self) -> bool {
self == &Self::None
}
}
impl FromStr for Login {
type Err = ();
fn from_str(src: &str) -> Result<Login, ()> {
return match src.to_lowercase().trim() {
"none" => Ok(Login::None),
"password" => Ok(Login::Password),
"access_token" | "access-token" | "accesstoken" => Ok(Login::AccessToken),
"sso" => Ok(Login::Sso),
_ => Err(()),
};
}
}
impl fmt::Display for Login {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self)
}
}
#[derive(Clone, Debug, Copy, PartialEq, Default, ValueEnum)]
enum Sync {
Off,
#[default]
Full,
}
impl Sync {
pub fn is_off(&self) -> bool {
self == &Self::Off
}
pub fn is_full(&self) -> bool {
self == &Self::Full
}
}
impl FromStr for Sync {
type Err = ();
fn from_str(src: &str) -> Result<Sync, ()> {
return match src.to_lowercase().trim() {
"off" => Ok(Sync::Off),
"full" => Ok(Sync::Full),
_ => Err(()),
};
}
}
impl fmt::Display for Sync {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self)
}
}
#[derive(Clone, Debug, Copy, PartialEq, Default, ValueEnum)]
enum Version {
#[default]
Check,
}
impl FromStr for Version {
type Err = ();
fn from_str(src: &str) -> Result<Version, ()> {
return match src.to_lowercase().trim() {
"check" => Ok(Version::Check),
_ => Err(()),
};
}
}
impl fmt::Display for Version {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self)
}
}
#[derive(Clone, Debug, Copy, PartialEq, Default, ValueEnum)]
enum Verify {
#[default]
None,
ManualDevice,
ManualUser,
Emoji,
EmojiReq,
}
impl Verify {
pub fn is_none(&self) -> bool {
self == &Self::None
}
pub fn is_manual_device(&self) -> bool {
self == &Self::ManualDevice
}
pub fn is_manual_user(&self) -> bool {
self == &Self::ManualUser
}
pub fn is_emoji(&self) -> bool {
self == &Self::Emoji
}
pub fn is_emoji_req(&self) -> bool {
self == &Self::EmojiReq
}
}
impl FromStr for Verify {
type Err = ();
fn from_str(src: &str) -> Result<Verify, ()> {
return match src.to_lowercase().trim() {
"none" => Ok(Verify::None),
"manual-device" => Ok(Verify::ManualDevice),
"manual-user" => Ok(Verify::ManualUser),
"emoji" => Ok(Verify::Emoji),
"emoji-req" => Ok(Verify::EmojiReq),
_ => Err(()),
};
}
}
impl fmt::Display for Verify {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self)
}
}
#[derive(Clone, Debug, Copy, PartialEq, Default, ValueEnum)]
enum Logout {
#[default]
None,
Me,
All,
}
impl Logout {
pub fn is_none(&self) -> bool {
self == &Self::None
}
pub fn is_me(&self) -> bool {
self == &Self::Me
}
pub fn is_all(&self) -> bool {
self == &Self::All
}
}
impl FromStr for Logout {
type Err = ();
fn from_str(src: &str) -> Result<Logout, ()> {
return match src.to_lowercase().trim() {
"none" => Ok(Logout::None),
"me" => Ok(Logout::Me),
"all" => Ok(Logout::All),
_ => Err(()),
};
}
}
impl fmt::Display for Logout {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self)
}
}
#[derive(Clone, Debug, Copy, PartialEq, Default, ValueEnum)]
enum Listen {
#[default]
Never,
Once,
Forever,
Tail,
All,
}
impl Listen {
pub fn is_never(&self) -> bool {
self == &Self::Never
}
pub fn is_once(&self) -> bool {
self == &Self::Once
}
pub fn is_forever(&self) -> bool {
self == &Self::Forever
}
pub fn is_tail(&self) -> bool {
self == &Self::Tail
}
pub fn is_all(&self) -> bool {
self == &Self::All
}
}
impl FromStr for Listen {
type Err = ();
fn from_str(src: &str) -> Result<Listen, ()> {
return match src.to_lowercase().trim() {
"never" => Ok(Listen::Never),
"once" => Ok(Listen::Once),
"forever" => Ok(Listen::Forever),
"tail" => Ok(Listen::Tail),
"all" => Ok(Listen::All),
_ => Err(()),
};
}
}
impl fmt::Display for Listen {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self)
}
}
#[derive(Clone, Debug, Copy, PartialEq, Default, ValueEnum)]
enum LogLevel {
#[default]
None,
Error,
Warn,
Info,
Debug,
Trace,
}
impl LogLevel {
pub fn is_none(&self) -> bool {
self == &Self::None
}
}
impl fmt::Display for LogLevel {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self)
}
}
#[derive(Clone, Debug, Copy, PartialEq, Default, ValueEnum)]
pub enum Output {
#[default]
Text,
Json,
JsonMax,
JsonSpec,
}
impl Output {
pub fn is_text(&self) -> bool {
self == &Self::Text
}
}
impl FromStr for Output {
type Err = ();
fn from_str(src: &str) -> Result<Output, ()> {
return match src.to_lowercase().replace('-', "_").trim() {
"text" => Ok(Output::Text),
"json" => Ok(Output::Json),
"jsonmax" | "json_max" => Ok(Output::JsonMax), "jsonspec" | "json_spec" => Ok(Output::JsonSpec),
_ => Err(()),
};
}
}
impl fmt::Display for Output {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self)
}
}
#[derive(Clone, Debug, Parser)]
#[command(author, version,
next_line_help = true,
bin_name = get_prog_without_ext(),
color = ColorChoice::Always,
term_width = 79,
after_help = "PS: Also have a look at scripts/matrix-commander-rs-tui.",
disable_version_flag = true,
disable_help_flag = true,
arg_required_else_help = true,
)]
pub struct Args {
#[arg(skip)]
creds: Option<Credentials>,
#[arg(long, default_value_t = false)]
contribute: bool,
#[arg(short, long, value_name = "CHECK")]
version: Option<Option<Version>>,
#[arg(long)]
usage: bool,
#[arg(short, long)]
help: bool,
#[arg(long)]
manual: bool,
#[arg(long)]
readme: bool,
#[arg(short, long, action = clap::ArgAction::Count, default_value_t = 0u8, )]
debug: u8,
#[arg(long, value_delimiter = ' ', num_args = 1..3, ignore_case = true, )]
log_level: Option<Vec<LogLevel>>,
#[arg(long, action = clap::ArgAction::Count, default_value_t = 0u8, )]
verbose: u8,
#[arg(long)]
plain: Option<bool>,
#[arg(short, long,
value_name = "PATH_TO_FILE",
value_parser = clap::value_parser!(PathBuf),
default_value_os_t = get_credentials_default_path(),
)]
credentials: PathBuf,
#[arg(short, long,
value_name = "PATH_TO_DIRECTORY",
// value_parser = clap::builder::ValueParser::path_buf(),
value_parser = clap::value_parser!(PathBuf),
default_value_os_t = get_store_default_path(),
)]
store: PathBuf,
#[arg(long, value_enum,
value_name = "LOGIN_METHOD",
default_value_t = Login::default(), ignore_case = true, )]
login: Login,
#[arg(long, value_enum,
value_name = "VERIFICATION_METHOD",
default_value_t = Verify::default(), ignore_case = true, )]
verify: Verify,
#[arg(long)]
bootstrap: bool,
#[arg(long, value_enum,
value_name = "DEVICE",
default_value_t = Logout::default(), ignore_case = true, )]
logout: Logout,
#[arg(long)]
homeserver: Option<Url>,
#[arg(long)]
user_login: Option<String>,
#[arg(long)]
password: Option<String>,
#[arg(long)]
device: Option<String>,
#[arg(long)]
room_default: Option<String>,
#[arg(long)]
devices: bool,
#[arg(long, default_value_t = TIMEOUT_DEFAULT)]
timeout: u64,
#[arg(short, long, num_args(0..), )]
message: Vec<String>,
#[arg(long)]
markdown: bool,
#[arg(long)]
code: bool,
#[arg(long)]
html: bool,
#[arg(short, long, num_args(0..), )]
room: Vec<String>,
#[arg(short, long, num_args(0..), )]
file: Vec<String>,
#[arg(long)]
notice: bool,
#[arg(long)]
emote: bool,
#[arg(long, value_enum,
value_name = "SYNC_TYPE",
default_value_t = Sync::default(), ignore_case = true, )]
sync: Sync,
#[arg(short, long, value_enum,
value_name = "LISTEN_TYPE",
default_value_t = Listen::default(), ignore_case = true, )]
listen: Listen,
#[arg(long, default_value_t = 0u64)]
tail: u64,
#[arg(short = 'y', long)]
listen_self: bool,
#[arg(long)]
whoami: bool,
#[arg(short, long, value_enum,
value_name = "OUTPUT_FORMAT",
default_value_t = Output::default(), ignore_case = true, )]
output: Output,
#[arg(long, num_args(0..), )]
file_name: Vec<PathBuf>,
#[arg(long, num_args(0..), value_name = "ROOM",
alias = "room-get-info")]
get_room_info: Vec<String>,
#[arg(long, num_args(0..), value_name = "LOCAL_ALIAS", )]
room_create: Vec<String>,
#[arg(long, value_enum,
value_name = "VISIBILITY",
default_value = Visibility::Private.as_str(), ignore_case = true, )]
visibility: Visibility,
#[arg(long, num_args(0..), value_name = "USER", )]
room_dm_create: Vec<String>,
#[arg(long, num_args(0..), value_name = "ROOM", )]
room_leave: Vec<String>,
#[arg(long, num_args(0..), value_name = "ROOM", )]
room_forget: Vec<String>,
#[arg(long, num_args(0..), value_name = "ROOM", )]
room_invite: Vec<String>,
#[arg(long, num_args(0..), value_name = "ROOM", )]
room_join: Vec<String>,
#[arg(long, num_args(0..), value_name = "ROOM", )]
room_ban: Vec<String>,
#[arg(long, num_args(0..), value_name = "ROOM", )]
room_unban: Vec<String>,
#[arg(long, num_args(0..), value_name = "ROOM", )]
room_kick: Vec<String>,
#[arg(long, num_args(0..), value_name = "ALIAS", )]
room_resolve_alias: Vec<String>,
#[arg(long, num_args(0..), value_name = "ROOM", )]
room_enable_encryption: Vec<String>,
#[arg(long, num_args(0..), value_name = "ALIAS", )]
alias: Vec<String>,
#[arg(long, num_args(0..), )]
name: Vec<String>,
#[arg(long, num_args(0..), )]
topic: Vec<String>,
#[arg(long)]
rooms: bool,
#[arg(long)]
invited_rooms: bool,
#[arg(long)]
joined_rooms: bool,
#[arg(long)]
left_rooms: bool,
#[arg(long, num_args(0..), alias = "get_room_visibility",
value_name = "ROOM", )]
room_get_visibility: Vec<String>,
#[arg(long, num_args(0..), alias = "get_room_state",
value_name = "ROOM", )]
room_get_state: Vec<String>,
#[arg(long, num_args(0..), value_name = "ROOM", )]
joined_members: Vec<String>,
#[arg(long, num_args(0..),
value_name = "DEVICE", )]
delete_device: Vec<String>,
#[arg(short, long, num_args(0..), )]
user: Vec<String>,
#[arg(long, value_name = "FILE")]
get_avatar: Option<PathBuf>,
#[arg(long, alias = "upload-avatar", value_name = "FILE")]
set_avatar: Option<PathBuf>,
#[arg(long)]
get_avatar_url: bool,
#[arg(long, alias = "upload-avatar-url", value_name = "MAX_URI")]
set_avatar_url: Option<OwnedMxcUri>,
#[arg(long, alias = "remove-avatar")]
unset_avatar_url: bool,
#[arg(long)]
get_display_name: bool,
#[arg(long, value_name = "NAME")]
set_display_name: Option<String>,
#[arg(long)]
get_profile: bool,
#[arg(long, alias = "upload", value_name = "FILE", num_args(0..), )]
media_upload: Vec<PathBuf>,
#[arg(long, alias = "download", value_name = "MXC_URI", num_args(0..), )]
media_download: Vec<OwnedMxcUri>,
#[arg(long, value_name = "MIME_TYPE", num_args(0..), )]
mime: Vec<String>,
#[arg(long, alias = "delete-mxc", value_name = "MXC_URI", num_args(0..), )]
media_delete: Vec<OwnedMxcUri>,
#[arg(long, alias = "mxc-to-http", value_name = "MXC_URI", num_args(0..), )]
media_mxc_to_http: Vec<OwnedMxcUri>,
#[arg(long)]
get_masterkey: bool,
}
impl Default for Args {
fn default() -> Self {
Self::new()
}
}
impl Args {
pub fn new() -> Args {
Args {
creds: None,
usage: false,
help: false,
manual: false,
readme: false,
contribute: false,
version: None,
debug: 0u8,
log_level: None,
verbose: 0u8,
plain: None,
credentials: get_credentials_default_path(),
store: get_store_default_path(),
login: Login::None,
bootstrap: false,
verify: Verify::None,
message: Vec::new(),
logout: Logout::None,
homeserver: None,
user_login: None,
password: None,
device: None,
room_default: None,
devices: false,
timeout: TIMEOUT_DEFAULT,
markdown: false,
code: false,
html: false,
room: Vec::new(),
file: Vec::new(),
notice: false,
emote: false,
sync: Sync::Full,
listen: Listen::Never,
tail: 0u64,
listen_self: false,
whoami: false,
output: Output::Text,
get_room_info: Vec::new(),
file_name: Vec::new(),
room_create: Vec::new(),
visibility: Visibility::Private,
room_dm_create: Vec::new(),
room_leave: Vec::new(),
room_forget: Vec::new(),
room_invite: Vec::new(),
room_join: Vec::new(),
room_ban: Vec::new(),
room_unban: Vec::new(),
room_kick: Vec::new(),
room_resolve_alias: Vec::new(),
room_enable_encryption: Vec::new(),
alias: Vec::new(),
name: Vec::new(),
topic: Vec::new(),
rooms: false,
invited_rooms: false,
joined_rooms: false,
left_rooms: false,
room_get_visibility: Vec::new(),
room_get_state: Vec::new(),
joined_members: Vec::new(),
delete_device: Vec::new(),
user: Vec::new(),
get_avatar: None,
set_avatar: None,
get_avatar_url: false,
set_avatar_url: None,
unset_avatar_url: false,
get_display_name: false,
set_display_name: None,
get_profile: false,
media_upload: Vec::new(),
media_download: Vec::new(),
media_delete: Vec::new(),
media_mxc_to_http: Vec::new(),
mime: Vec::new(),
get_masterkey: false,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Credentials {
homeserver: Url,
user_id: OwnedUserId,
access_token: String,
device_id: OwnedDeviceId,
room_id: String,
refresh_token: Option<String>,
}
impl AsRef<Credentials> for Credentials {
fn as_ref(&self) -> &Self {
self
}
}
impl Credentials {
fn new(
homeserver: Url,
user_id: OwnedUserId,
access_token: String,
device_id: OwnedDeviceId,
room_id: String,
refresh_token: Option<String>,
) -> Self {
Self {
homeserver,
user_id,
access_token,
device_id,
room_id,
refresh_token,
}
}
fn load(path: &Path) -> Result<Credentials, Error> {
let reader = File::open(path)?;
Credentials::set_permissions(&reader)?;
let credentials: Credentials = serde_json::from_reader(reader)?;
let mut credentialsfiltered = credentials.clone();
credentialsfiltered.access_token = "***".to_string();
info!("loaded credentials are: {:?}", credentialsfiltered);
Ok(credentials)
}
fn save(&self, path: &Path) -> Result<(), Error> {
fs::create_dir_all(path.parent().ok_or(Error::NoHomeDirectory)?)?;
let writer = File::create(path)?;
serde_json::to_writer_pretty(&writer, self)?;
Credentials::set_permissions(&writer)?;
Ok(())
}
#[cfg(unix)]
fn set_permissions(file: &File) -> Result<(), Error> {
use std::os::unix::fs::PermissionsExt;
let perms = file.metadata()?.permissions();
if perms.mode() & 0o4 == 0o4 {
file.set_permissions(fs::Permissions::from_mode(0o600))
.unwrap();
}
Ok(())
}
#[cfg(not(unix))]
fn set_permissions(file: &File) -> Result<(), Error> {
Ok(())
}
}
impl From<Credentials> for SessionMeta {
fn from(creditials: Credentials) -> Self {
Self {
user_id: creditials.user_id,
device_id: creditials.device_id,
}
}
}
fn get_credentials_default_path() -> PathBuf {
let dir = ProjectDirs::from_path(PathBuf::from(get_prog_without_ext())).unwrap();
let dp = dir.data_dir().join(CREDENTIALS_FILE_DEFAULT);
debug!(
"Data will be put into project directory {:?}.",
dir.data_dir()
);
info!("Credentials file with access token is {}.", dp.display());
dp
}
fn get_credentials_actual_path(ap: &Args) -> &PathBuf {
&ap.credentials
}
fn credentials_exist(ap: &Args) -> bool {
let dp = get_credentials_default_path();
let ap = get_credentials_actual_path(ap);
debug!(
"credentials_default_path = {:?}, credentials_actual_path = {:?}",
dp, ap
);
let exists = ap.is_file();
if exists {
debug!("{:?} exists and is file. Not sure if readable though.", ap);
} else {
debug!("{:?} does not exist or is not a file.", ap);
}
exists
}
fn get_store_default_path() -> PathBuf {
let dir = ProjectDirs::from_path(PathBuf::from(get_prog_without_ext())).unwrap();
let dp = dir.data_dir().join(STORE_DIR_DEFAULT);
debug!("Default project directory is {:?}.", dir.data_dir());
info!("Default store directory is {}.", dp.display());
dp
}
fn get_store_actual_path(ap: &Args) -> &PathBuf {
&ap.store
}
#[allow(dead_code)]
fn store_exist(ap: &Args) -> bool {
let dp = get_store_default_path();
let ap = get_store_actual_path(ap);
debug!(
"store_default_path = {:?}, store_actual_path = {:?}",
dp, ap
);
let exists = ap.is_dir();
if exists {
debug!(
"{:?} exists and is directory. Not sure if readable though.",
ap
);
} else {
debug!("{:?} does not exist or is not a directory.", ap);
}
exists
}
fn get_version() -> &'static str {
VERSION_O.unwrap_or(VERSION)
}
fn get_pkg_name() -> &'static str {
PKG_NAME_O.unwrap_or(PKG_NAME)
}
fn get_bin_name() -> &'static str {
BIN_NAME_O.unwrap_or(BIN_NAME)
}
fn get_pkg_repository() -> &'static str {
PKG_REPOSITORY_O.unwrap_or(PKG_REPOSITORY)
}
fn get_prog_without_ext() -> &'static str {
get_bin_name() }
pub fn usage() {
let help_str = Args::command().render_usage().to_string();
println!("{}", &help_str);
println!("Options:");
let help_str = Args::command().render_help().to_string();
let v: Vec<&str> = help_str.split('\n').collect();
for l in v {
if l.starts_with(" -") || l.starts_with(" --") {
println!("{}", &l);
}
}
}
pub fn help() {
let help_str = Args::command().render_help().to_string();
let re = Regex::new(r"(?P<del>[ ]+Details::[\S\s]*?)(?P<keep>\nPS:|\n -|\n --)").unwrap();
let after = re.replace_all(&help_str, "$keep");
print!("{}", &after.replace("\n\n", "\n")); println!("Use --manual to get more detailed help information.");
}
pub fn manual() {
let help_str = Args::command().render_long_help().to_string();
println!("{}", &help_str);
}
pub async fn readme() {
match reqwest::get(URL_README).await {
Ok(resp) => {
debug!("Got README.md file from URL {:?}.", URL_README);
println!("{}", resp.text().await.unwrap())
}
Err(ref e) => {
println!(
"Error getting README.md from {:#?}. Reported error {:?}.",
URL_README, e
);
}
};
}
pub fn version(output: Output) {
let version = if stdout().is_terminal() {
get_version().green()
} else {
get_version().normal()
};
match output {
Output::Text => {
println!();
println!(
" _| _| _|_|_| {}",
get_prog_without_ext()
);
print!(" _|_| _|_| _| _~^~^~_ ");
println!("a rusty vision of a Matrix CLI client");
println!(
" _| _| _| _| \\) / o o \\ (/ version {}",
version
);
println!(
" _| _| _| '_ - _' repo {}",
get_pkg_repository()
);
print!(" _| _| _|_|_| / '-----' \\ ");
println!("please submit PRs to make the vision a reality");
println!();
}
Output::JsonSpec => (),
_ => println!(
"{{\"program\": {:?}, \"version\": {:?}, \"repo\": {:?}}}",
get_prog_without_ext(),
get_version(),
get_pkg_repository()
),
}
}
pub fn version_check() {
println!("Installed version: v{}", get_version());
let name = env!("CARGO_PKG_NAME");
let version = env!("CARGO_PKG_VERSION");
let informer = update_informer::new(registry::Crates, name, version).check_version();
let avail = "New version is available";
let uptod = "You are up-to-date.";
let couldnot = "Could not get latest version.";
let available;
let uptodate;
let couldnotget;
if stdout().is_terminal() {
available = avail.yellow();
uptodate = uptod.green();
couldnotget = couldnot.red();
} else {
available = avail.normal();
uptodate = uptod.normal();
couldnotget = couldnot.normal();
}
match informer {
Ok(Some(version)) => println!(
"{} on https://crates.io/crates/{}: {}",
available, name, version
),
Ok(None) => {
println!("{uptodate} You already have the latest version.")
}
Err(ref e) => println!("{couldnotget} Error reported: {e}."),
};
}
pub fn contribute() {
println!();
println!(
"This project is currently only a vision. The Python package {} exists. ",
get_prog_without_ext()
);
println!("The vision is to have a compatible program in Rust. I cannot do it myself, ");
println!("but I can coordinate and merge your pull requests. Have a look at the repo ");
println!("{}. Please help! Please contribute ", get_pkg_repository());
println!("code to make this vision a reality, and to one day have a functional ");
println!("{} crate. Safe!", get_prog_without_ext());
}
fn get_homeserver(ap: &mut Args) {
while ap.homeserver.is_none() {
print!("Enter your Matrix homeserver (e.g. https://some.homeserver.org): ");
if let Err(e) = io::stdout().flush() {
warn!("Warning: Failed to flush stdout: {e}");
}
let mut input = String::new();
if io::stdin().read_line(&mut input).is_err() {
error!("Error: Unable to read user input");
continue; }
let trimmed_input = input.trim();
if trimmed_input.is_empty() {
error!("Error: Empty homeserver name is not allowed!");
} else if let Err(e) = Url::parse(trimmed_input) {
error!(
"Error: The syntax is incorrect. Homeserver must be a valid URL! \
Start with 'http://' or 'https://'. Details: {e}"
);
} else {
ap.homeserver = Some(Url::parse(trimmed_input).unwrap()); debug!("homeserver is {}", ap.homeserver.as_ref().unwrap());
}
}
}
fn get_user_login(ap: &mut Args) {
while ap.user_login.is_none() {
print!("Enter your full Matrix username (e.g. @john:some.homeserver.org): ");
if let Err(e) = io::stdout().flush() {
warn!("Warning: Failed to flush stdout: {e}");
}
let mut input = String::new();
if io::stdin().read_line(&mut input).is_err() {
error!("Error: Unable to read user input");
continue; }
let trimmed_input = input.trim();
if trimmed_input.is_empty() {
error!("Error: Empty username is not allowed!");
} else if !is_valid_username(trimmed_input) {
error!("Error: Invalid username format!");
} else {
ap.user_login = Some(trimmed_input.to_string());
debug!("user_login is {trimmed_input}");
}
}
}
fn is_valid_username(username: &str) -> bool {
username.starts_with('@') && username.contains(':')
}
fn get_password(ap: &mut Args) {
while ap.password.is_none() {
print!("Enter Matrix password for this user: ");
if let Err(e) = io::stdout().flush() {
warn!("Warning: Failed to flush stdout: {e}");
}
match read_password() {
Ok(password) => {
let trimmed_password = password.trim();
if trimmed_password.is_empty() {
error!("Error: Empty password is not allowed!");
} else {
ap.password = Some(password);
debug!("password is {}", "******");
}
}
Err(e) => {
error!("Error reading password: {e}");
}
}
}
}
fn get_device(ap: &mut Args) {
while ap.device.is_none() {
print!(
"Enter your desired name for the Matrix device that \
is going to be created for you (e.g. {}): ",
get_prog_without_ext()
);
if let Err(e) = io::stdout().flush() {
warn!("Warning: Failed to flush stdout: {e}");
}
let mut input = String::new();
if io::stdin().read_line(&mut input).is_err() {
error!("Error: Unable to read user input");
continue; }
let trimmed_input = input.trim();
if trimmed_input.is_empty() {
error!("Error: Empty device name is not allowed!");
} else {
ap.device = Some(trimmed_input.to_string());
debug!("device is {trimmed_input}");
}
}
}
fn get_room_default(ap: &mut Args) {
while ap.room_default.is_none() {
print!(
"Enter name of one of your Matrix rooms that you want to use as default room \
(e.g. !someRoomId:some.homeserver.org): "
);
if let Err(e) = io::stdout().flush() {
warn!("Warning: Failed to flush stdout: {e}");
}
let mut input = String::new();
if io::stdin().read_line(&mut input).is_err() {
error!("Error: Unable to read user input");
continue; }
let trimmed_input = input.trim();
if trimmed_input.is_empty() {
error!("Error: Empty name of default room is not allowed!");
} else if !is_valid_room_name(trimmed_input) {
error!("Error: Invalid room name format for '{trimmed_input}'! Room name must start with '!' and contain exactly one ':'.");
} else {
ap.room_default = Some(trimmed_input.to_string());
debug!("room_default is '{trimmed_input}'");
}
}
}
fn is_valid_room_name(name: &str) -> bool {
name.starts_with('!') && name.matches(':').count() == 1
}
fn set_rooms(ap: &mut Args, default_room: &str) {
debug!("set_rooms()");
if ap.room.is_empty() {
ap.room.push(default_room.to_string()); }
}
async fn get_room_default_from_credentials(client: &Client, credentials: &Credentials) -> String {
let mut room = credentials.room_id.clone();
convert_to_full_room_id(
client,
&mut room,
credentials.homeserver.host_str().unwrap(),
)
.await;
room
}
fn set_users(ap: &mut Args) {
debug!("set_users()");
if ap.user.is_empty() {
let duser = get_user_default_from_credentials(ap.creds.as_ref().unwrap());
ap.user.push(duser.to_string()); }
}
#[allow(dead_code)]
fn get_users(ap: &Args) -> &Vec<String> {
debug!("get_users()");
&ap.user
}
fn get_user_default_from_credentials(credentials: &Credentials) -> OwnedUserId {
credentials.user_id.clone()
}
fn convert_to_full_room_aliases(vecstr: &mut Vec<String>, default_host: &str) {
vecstr.retain(|x| !x.trim().is_empty());
for el in vecstr {
el.retain(|c| !c.is_whitespace());
if el.starts_with('!') {
warn!("A room id was given as alias. {:?}", el);
continue;
}
if !el.starts_with('#') {
el.insert(0, '#');
}
if !el.contains(':') {
el.push(':');
el.push_str(default_host);
}
}
}
fn replace_minus_with_default_room(vecstr: &mut Vec<String>, default_room: &str) {
if vecstr.iter().any(|x| x.trim() == "-") {
vecstr.push(default_room.to_string());
}
vecstr.retain(|x| x.trim() != "-");
}
pub(crate) async fn cli_login(ap: &mut Args) -> Result<(Client, Credentials), Error> {
if ap.login.is_none() {
return Err(Error::UnsupportedCliParameter("--login cannot be empty"));
}
if credentials_exist(ap) {
error!(concat!(
"Credentials file already exists. You have already logged in in ",
"the past. No login needed. Skipping login. If you really want to log in ",
"(i.e. create a new device), then logout first, or move credentials file manually. ",
"Or just run your command again but without the '--login' option to log in ",
"via your existing credentials and access token. ",
));
return Err(Error::LoginUnnecessary);
}
if !ap.login.is_password() {
error!(
"Login option '{:?}' currently not supported. Use '{:?}' for the time being.",
ap.login,
Login::Password
);
return Err(Error::UnsupportedCliParameter(
"Used login option currently not supported. Use 'password' for the time being.",
));
}
get_homeserver(ap);
get_user_login(ap);
get_password(ap);
get_device(ap); get_room_default(ap);
info!(
"Parameters for login are: {:?} {:?} {:?} {:?} {:?}",
ap.homeserver, ap.user_login, "******", ap.device, ap.room_default
);
let (client, credentials) = crate::login(
ap,
&ap.homeserver.clone().ok_or(Error::MissingCliParameter)?,
&ap.user_login.clone().ok_or(Error::MissingCliParameter)?,
&ap.password.clone().ok_or(Error::MissingCliParameter)?,
&ap.device.clone().ok_or(Error::MissingCliParameter)?,
&ap.room_default.clone().ok_or(Error::MissingCliParameter)?,
)
.await?;
Ok((client, credentials))
}
pub(crate) async fn cli_restore_login(
credentials: &Credentials,
ap: &Args,
) -> Result<Client, Error> {
info!("restore_login implicitly chosen.");
crate::restore_login(credentials, ap).await
}
pub(crate) async fn cli_bootstrap(client: &Client, ap: &mut Args) -> Result<(), Error> {
info!("Bootstrap chosen.");
crate::bootstrap(client, ap).await
}
pub(crate) async fn cli_verify(client: &Client, ap: &Args) -> Result<(), Error> {
info!("Verify chosen.");
if ap.verify.is_none() {
return Err(Error::UnsupportedCliParameter(
"Argument --verify cannot be empty",
));
}
if !ap.verify.is_manual_device()
&& !ap.verify.is_manual_user()
&& !ap.verify.is_emoji()
&& !ap.verify.is_emoji_req()
{
error!(
"Verify option '{:?}' currently not supported. \
Use '{:?}', '{:?}', '{:?}' or {:?}' for the time being.",
ap.verify,
Verify::ManualDevice,
Verify::ManualUser,
Verify::Emoji,
Verify::EmojiReq
);
return Err(Error::UnsupportedCliParameter(
"Used --verify option is currently not supported",
));
}
crate::verify(client, ap).await
}
fn trim_newline(s: &mut String) -> &mut String {
if s.ends_with('\n') {
s.pop();
if s.ends_with('\r') {
s.pop();
}
}
s
}
pub(crate) async fn cli_message(client: &Client, ap: &Args) -> Result<(), Error> {
info!("Message chosen.");
if ap.message.is_empty() {
return Ok(()); }
let mut fmsgs: Vec<String> = Vec::new(); for msg in ap.message.iter() {
if msg.is_empty() {
info!("Skipping empty text message.");
continue;
};
if msg == "--" {
info!("Skipping '--' text message as these are used to separate arguments.");
continue;
};
let fmsg = if msg == r"-" {
let mut line = String::new();
if stdin().is_terminal() {
print!("Message: ");
io::stdout().flush()?;
io::stdin().read_line(&mut line)?;
} else {
io::stdin().read_to_string(&mut line)?;
}
line.strip_suffix("\r\n")
.or(line.strip_suffix("\n"))
.unwrap_or(&line)
.to_string() } else if msg == r"_" {
let mut eof = false;
while !eof {
let mut line = String::new();
match io::stdin().read_line(&mut line) {
Ok(n) => {
if n == 0 {
eof = true;
debug!("Reached EOF of pipe stream.");
} else {
debug!(
"Read {n} bytes containing \"{}\\n\" from pipe stream.",
trim_newline(&mut line.clone())
);
match message(
client,
&[line],
&ap.room,
ap.code,
ap.markdown,
ap.notice,
ap.emote,
ap.html,
)
.await
{
Ok(()) => {
debug!("message from pipe stream sent successfully");
}
Err(ref e) => {
error!(
"Error: sending message from pipe stream reported {}",
e
);
}
}
}
}
Err(ref e) => {
error!("Error: reading from pipe stream reported {}", e);
}
}
}
"".to_owned()
} else if msg == r"\-" {
"-".to_string()
} else if msg == r"\_" {
"_".to_string()
} else if msg == r"\-\-" {
"--".to_string()
} else if msg == r"\-\-\-" {
"---".to_string()
} else {
msg.to_string()
};
if !fmsg.is_empty() {
fmsgs.push(fmsg);
}
}
if fmsgs.is_empty() {
return Ok(()); }
message(
client,
&fmsgs,
&ap.room,
ap.code,
ap.markdown,
ap.notice,
ap.emote,
ap.html,
)
.await }
pub(crate) async fn cli_file(client: &Client, ap: &Args) -> Result<(), Error> {
info!("File chosen.");
if ap.file.is_empty() {
return Ok(()); }
let mut files: Vec<PathBuf> = Vec::new();
for filename in &ap.file {
match filename.as_str() {
"" => info!("Skipping empty file name."),
r"-" => files.push(PathBuf::from("-".to_string())),
r"\-" => files.push(PathBuf::from(r"\-".to_string())),
_ => files.push(PathBuf::from(filename)),
}
}
let pb: PathBuf = if !ap.file_name.is_empty() {
ap.file_name[0].clone()
} else {
PathBuf::from("file")
};
file(
client, &files, &ap.room, None, None, &pb, )
.await }
pub(crate) async fn cli_media_upload(client: &Client, ap: &Args) -> Result<(), Error> {
info!("Media upload chosen.");
media_upload(client, &ap.media_upload, &ap.mime, ap.output).await }
pub(crate) async fn cli_media_download(client: &Client, ap: &Args) -> Result<(), Error> {
info!("Media download chosen.");
media_download(client, &ap.media_download, &ap.file_name, ap.output).await }
pub(crate) async fn cli_media_delete(client: &Client, ap: &Args) -> Result<(), Error> {
info!("Media delete chosen.");
media_delete(client, &ap.media_delete, ap.output).await }
pub(crate) async fn cli_media_mxc_to_http(ap: &Args) -> Result<(), Error> {
info!("Media mxc_to_http chosen.");
media_mxc_to_http(
&ap.media_mxc_to_http,
&ap.creds.as_ref().unwrap().homeserver,
ap.output,
)
.await }
pub(crate) async fn cli_listen_once(client: &Client, ap: &Args) -> Result<(), Error> {
info!("Listen Once chosen.");
listen_once(client, ap.listen_self, crate::whoami(ap), ap.output).await }
pub(crate) async fn cli_listen_forever(client: &Client, ap: &Args) -> Result<(), Error> {
info!("Listen Forever chosen.");
listen_forever(client, ap.listen_self, crate::whoami(ap), ap.output).await
}
pub(crate) async fn cli_listen_tail(client: &Client, ap: &Args) -> Result<(), Error> {
info!("Listen Tail chosen.");
listen_tail(
client,
&ap.room,
ap.tail,
ap.listen_self,
crate::whoami(ap),
ap.output,
)
.await }
pub(crate) async fn cli_listen_all(client: &Client, ap: &Args) -> Result<(), Error> {
info!("Listen All chosen.");
listen_all(
client,
&ap.room,
ap.listen_self,
crate::whoami(ap),
ap.output,
)
.await }
pub(crate) async fn cli_devices(client: &Client, ap: &Args) -> Result<(), Error> {
info!("Devices chosen.");
crate::devices(client, ap.output).await }
pub(crate) fn whoami(ap: &Args) -> OwnedUserId {
ap.creds.as_ref().unwrap().user_id.clone()
}
pub(crate) fn cli_whoami(ap: &Args) -> Result<(), Error> {
info!("Whoami chosen.");
let whoami = crate::whoami(ap);
match ap.output {
Output::Text => println!("{}", whoami),
Output::JsonSpec => (),
_ => println!("{{\"user_id\": \"{}\"}}", whoami),
}
Ok(())
}
pub(crate) async fn cli_get_room_info(client: &Client, ap: &Args) -> Result<(), Error> {
info!("Get-room-info chosen.");
crate::get_room_info(client, &ap.get_room_info, ap.output).await
}
pub(crate) async fn cli_rooms(client: &Client, ap: &Args) -> Result<(), Error> {
info!("Rooms chosen.");
crate::rooms(client, ap.output).await
}
pub(crate) async fn cli_invited_rooms(client: &Client, ap: &Args) -> Result<(), Error> {
info!("Invited-rooms chosen.");
crate::invited_rooms(client, ap.output).await
}
pub(crate) async fn cli_joined_rooms(client: &Client, ap: &Args) -> Result<(), Error> {
info!("Joined-rooms chosen.");
crate::joined_rooms(client, ap.output).await
}
pub(crate) async fn cli_left_rooms(client: &Client, ap: &Args) -> Result<(), Error> {
info!("Left-rooms chosen.");
crate::left_rooms(client, ap.output).await
}
pub(crate) async fn cli_room_create(client: &Client, ap: &Args) -> Result<(), Error> {
info!("Room-create chosen.");
crate::room_create(
client,
false,
match &ap.visibility {
Visibility::Private => !ap.plain.unwrap_or(false), Visibility::Public => !ap.plain.unwrap_or(true), _ => !ap.plain.unwrap_or(false),
},
&[],
&ap.room_create,
&ap.name,
&ap.topic,
ap.output,
ap.visibility.clone(),
)
.await
}
pub(crate) async fn cli_room_dm_create(client: &Client, ap: &Args) -> Result<(), Error> {
info!("Room-dm-create chosen.");
crate::room_create(
client,
true,
match &ap.visibility {
Visibility::Private => !ap.plain.unwrap_or(false), Visibility::Public => !ap.plain.unwrap_or(true), _ => !ap.plain.unwrap_or(false),
},
&ap.room_dm_create,
&ap.alias,
&ap.name,
&ap.topic,
ap.output,
ap.visibility.clone(),
)
.await
}
pub(crate) async fn cli_room_leave(client: &Client, ap: &Args) -> Result<(), Error> {
info!("Room-leave chosen.");
crate::room_leave(client, &ap.room_leave, ap.output).await
}
pub(crate) async fn cli_room_forget(client: &Client, ap: &Args) -> Result<(), Error> {
info!("Room-forget chosen.");
crate::room_forget(client, &ap.room_forget, ap.output).await
}
pub(crate) async fn cli_room_invite(client: &Client, ap: &Args) -> Result<(), Error> {
info!("Room-invite chosen.");
crate::room_invite(client, &ap.room_invite, &ap.user, ap.output).await
}
pub(crate) async fn cli_room_join(client: &Client, ap: &Args) -> Result<(), Error> {
info!("Room-join chosen.");
crate::room_join(client, &ap.room_join, ap.output).await
}
pub(crate) async fn cli_room_ban(client: &Client, ap: &Args) -> Result<(), Error> {
info!("Room-ban chosen.");
crate::room_ban(client, &ap.room_ban, &ap.user, ap.output).await
}
pub(crate) async fn cli_room_unban(client: &Client, ap: &Args) -> Result<(), Error> {
info!("Room-unban chosen.");
crate::room_unban(client, &ap.room_unban, &ap.user, ap.output).await
}
pub(crate) async fn cli_room_kick(client: &Client, ap: &Args) -> Result<(), Error> {
info!("Room-kick chosen.");
crate::room_kick(client, &ap.room_kick, &ap.user, ap.output).await
}
pub(crate) async fn cli_room_resolve_alias(client: &Client, ap: &Args) -> Result<(), Error> {
info!("Room-resolve-alias chosen.");
crate::room_resolve_alias(client, &ap.room_resolve_alias, ap.output).await
}
pub(crate) async fn cli_room_enable_encryption(client: &Client, ap: &Args) -> Result<(), Error> {
info!("Room-enable-encryption chosen.");
crate::room_enable_encryption(client, &ap.room_enable_encryption, ap.output).await
}
pub(crate) async fn cli_get_avatar(client: &Client, ap: &Args) -> Result<(), Error> {
info!("Get-avatar chosen.");
if let Some(path) = ap.get_avatar.as_ref() {
crate::get_avatar(client, path, ap.output).await
} else {
Err(Error::MissingCliParameter)
}
}
pub(crate) async fn cli_set_avatar(client: &Client, ap: &Args) -> Result<(), Error> {
info!("Set-avatar chosen.");
if let Some(path) = ap.set_avatar.as_ref() {
crate::set_avatar(client, path, ap.output).await
} else {
Err(Error::MissingCliParameter)
}
}
pub(crate) async fn cli_get_avatar_url(client: &Client, ap: &Args) -> Result<(), Error> {
info!("Get-avatar-url chosen.");
crate::get_avatar_url(client, ap.output).await
}
pub(crate) async fn cli_set_avatar_url(client: &Client, ap: &Args) -> Result<(), Error> {
info!("Set-avatar-url chosen.");
if let Some(mxc_uri) = ap.set_avatar_url.as_ref() {
crate::set_avatar_url(client, mxc_uri, ap.output).await
} else {
Err(Error::MissingCliParameter)
}
}
pub(crate) async fn cli_unset_avatar_url(client: &Client, ap: &Args) -> Result<(), Error> {
info!("Unset-avatar-url chosen.");
crate::unset_avatar_url(client, ap.output).await
}
pub(crate) async fn cli_get_display_name(client: &Client, ap: &Args) -> Result<(), Error> {
info!("Get-display-name chosen.");
crate::get_display_name(client, ap.output).await
}
pub(crate) async fn cli_set_display_name(client: &Client, ap: &Args) -> Result<(), Error> {
info!("Set-display-name chosen.");
if let Some(name) = ap.set_display_name.as_ref() {
crate::set_display_name(client, name, ap.output).await
} else {
Err(Error::MissingCliParameter)
}
}
pub(crate) async fn cli_get_profile(client: &Client, ap: &Args) -> Result<(), Error> {
info!("Get-profile chosen.");
crate::get_profile(client, ap.output).await
}
pub(crate) async fn cli_get_masterkey(client: &Client, ap: &Args) -> Result<(), Error> {
info!("Get-masterkey chosen.");
crate::get_masterkey(
client,
ap.creds.as_ref().unwrap().user_id.clone(),
ap.output,
)
.await
}
pub(crate) async fn cli_room_get_visibility(client: &Client, ap: &Args) -> Result<(), Error> {
info!("Room-get-visibility chosen.");
crate::room_get_visibility(client, &ap.room_get_visibility, ap.output).await
}
pub(crate) async fn cli_room_get_state(client: &Client, ap: &Args) -> Result<(), Error> {
info!("Room-get-state chosen.");
crate::room_get_state(client, &ap.room_get_state, ap.output).await
}
pub(crate) async fn cli_joined_members(client: &Client, ap: &Args) -> Result<(), Error> {
info!("Joined-members chosen.");
crate::joined_members(client, &ap.joined_members, ap.output).await
}
pub(crate) async fn cli_delete_device(client: &Client, ap: &mut Args) -> Result<(), Error> {
info!("Delete-device chosen.");
crate::delete_devices_pre(client, ap).await
}
pub(crate) async fn cli_logout(client: &Client, ap: &mut Args) -> Result<(), Error> {
info!("Logout chosen.");
if ap.logout.is_none() {
return Ok(());
}
if ap.logout.is_all() {
ap.delete_device = vec!["*".to_owned()];
match cli_delete_device(client, ap).await {
Ok(_) => info!("Logout caused all devices to be deleted."),
Err(e) => error!(
"Error: Failed to delete all devices, but we remove local device id anyway. {:?}",
e
),
}
}
crate::logout(client, ap).await
}
#[tokio::main]
async fn main() -> Result<(), Error> {
let mut ap = Args::parse();
let mut errcount = 0;
let mut result: Result<(), Error> = Ok(());
let env_org_rust_log = env::var("RUST_LOG").unwrap_or_default().to_uppercase();
match ap.debug.cmp(&1) {
Ordering::Equal => {
let llvec = vec![LogLevel::Debug];
ap.log_level = Some(llvec);
}
Ordering::Greater => {
let mut llvec = vec![LogLevel::Debug];
llvec.push(LogLevel::Debug);
ap.log_level = Some(llvec);
}
Ordering::Less => (),
}
match ap.log_level.clone() {
None => {
tracing_subscriber::fmt()
.with_writer(io::stderr)
.with_env_filter(EnvFilter::from_default_env()) .init();
debug!(
"Neither --debug nor --log-level was used. Using environment variable RUST_LOG."
);
}
Some(llvec) => {
if llvec.len() == 1 {
if llvec[0].is_none() {
return Err(Error::UnsupportedCliParameter(
"Value 'none' not allowed for --log-level argument",
));
}
let mut rlogstr: String = BIN_NAME_UNDERSCORE.to_owned();
rlogstr.push('='); rlogstr.push_str(&llvec[0].to_string());
tracing_subscriber::fmt()
.with_writer(io::stderr)
.with_env_filter(rlogstr.clone()) .init();
debug!(
"The --debug or --log-level was used once or with one value. \
Specifying logging equivalent to RUST_LOG setting of '{}'.",
rlogstr
);
} else {
if llvec[0].is_none() || llvec[1].is_none() {
return Err(Error::UnsupportedCliParameter(
"Value 'none' not allowed for --log-level argument",
));
}
let mut rlogstr: String = llvec[1].to_string().to_owned();
rlogstr.push(','); rlogstr.push_str(BIN_NAME_UNDERSCORE);
rlogstr.push('=');
rlogstr.push_str(&llvec[0].to_string());
tracing_subscriber::fmt()
.with_writer(io::stderr)
.with_env_filter(rlogstr.clone())
.init();
debug!(
"The --debug or --log-level was used twice or with two values. \
Specifying logging equivalent to RUST_LOG setting of '{}'.",
rlogstr
);
}
if llvec.len() > 2 {
debug!("The --log-level option was incorrectly used more than twice. Ignoring third and further use.")
}
}
}
if ap.debug > 0 {
info!("The --debug option overwrote the --log-level option.")
}
if ap.debug > 2 {
debug!("The --debug option was incorrectly used more than twice. Ignoring third and further use.")
}
debug!("Original RUST_LOG env var is '{}'", env_org_rust_log);
debug!(
"Final RUST_LOG env var is '{}'",
env::var("RUST_LOG").unwrap_or_default().to_uppercase()
);
debug!("Final log-level option is {:?}", ap.log_level);
if enabled!(Level::TRACE) {
debug!(
"Log level of module {} is set to TRACE.",
get_prog_without_ext()
);
} else if enabled!(Level::DEBUG) {
debug!(
"Log level of module {} is set to DEBUG.",
get_prog_without_ext()
);
}
debug!("Version is {}", get_version());
debug!("Package name is {}", get_pkg_name());
debug!("Repo is {}", get_pkg_repository());
debug!("contribute flag is {}", ap.contribute);
debug!("version option is set to {:?}", ap.version);
debug!("debug flag is {}", ap.debug);
debug!("log-level option is {:?}", ap.log_level);
debug!("verbose option is {}", ap.verbose);
debug!("plain flag is {:?}", ap.plain);
debug!("credentials option is {:?}", ap.credentials);
debug!("store option is {:?}", ap.store);
debug!("login option is {:?}", ap.login);
debug!("bootstrap flag is {:?}", ap.bootstrap);
debug!("verify flag is {:?}", ap.verify);
debug!("message option is {:?}", ap.message);
debug!("logout option is {:?}", ap.logout);
debug!("homeserver option is {:?}", ap.homeserver);
debug!("user-login option is {:?}", ap.user_login);
debug!("password option is {:?}", ap.password);
debug!("device option is {:?}", ap.device);
debug!("room-default option is {:?}", ap.room_default);
debug!("devices flag is {:?}", ap.devices);
debug!("timeout option is {:?}", ap.timeout);
debug!("markdown flag is {:?}", ap.markdown);
debug!("code flag is {:?}", ap.code);
debug!("room option is {:?}", ap.room);
debug!("file option is {:?}", ap.file);
debug!("notice flag is {:?}", ap.notice);
debug!("emote flag is {:?}", ap.emote);
debug!("sync option is {:?}", ap.sync);
debug!("listen option is {:?}", ap.listen);
debug!("tail option is {:?}", ap.tail);
debug!("listen-self flag is {:?}", ap.listen_self);
debug!("whoami flag is {:?}", ap.whoami);
debug!("output option is {:?}", ap.output);
debug!("get-room-info option is {:?}", ap.get_room_info);
debug!("file-name option is {:?}", ap.file_name);
debug!("room-create option is {:?}", ap.room_create);
debug!("room-dm-create option is {:?}", ap.room_dm_create);
debug!("room-leave option is {:?}", ap.room_leave);
debug!("room-forget option is {:?}", ap.room_forget);
debug!("room-invite option is {:?}", ap.room_invite);
debug!("room-join option is {:?}", ap.room_join);
debug!("room-ban option is {:?}", ap.room_ban);
debug!("room-unban option is {:?}", ap.room_unban);
debug!("room-kick option is {:?}", ap.room_kick);
debug!("room-resolve-alias option is {:?}", ap.room_resolve_alias);
debug!(
"room-enable-encryption option is {:?}",
ap.room_enable_encryption
);
debug!("alias option is {:?}", ap.alias);
debug!("name option is {:?}", ap.name);
debug!("topic-create option is {:?}", ap.topic);
debug!("rooms option is {:?}", ap.rooms);
debug!("invited-rooms option is {:?}", ap.invited_rooms);
debug!("joined-rooms option is {:?}", ap.joined_rooms);
debug!("left-rooms option is {:?}", ap.left_rooms);
debug!("room-get-visibility option is {:?}", ap.room_get_visibility);
debug!("room-get-state option is {:?}", ap.room_get_state);
debug!("joined-members option is {:?}", ap.joined_members);
debug!("delete-device option is {:?}", ap.delete_device);
debug!("user option is {:?}", ap.user);
debug!("get-avatar option is {:?}", ap.get_avatar);
debug!("set-avatar option is {:?}", ap.set_avatar);
debug!("get-avatar_url flag is {:?}", ap.get_avatar_url);
debug!("set-avatar_url option is {:?}", ap.set_avatar_url);
debug!("unset-avatar_url flag is {:?}", ap.unset_avatar_url);
debug!("get-display-name option is {:?}", ap.get_display_name);
debug!("set-display-name option is {:?}", ap.set_display_name);
debug!("get-profile option is {:?}", ap.get_profile);
debug!("media-upload option is {:?}", ap.media_upload);
debug!("media-download option is {:?}", ap.media_download);
debug!("media-delete option is {:?}", ap.media_delete);
debug!("media-mxc-to-http option is {:?}", ap.media_mxc_to_http);
debug!("mime option is {:?}", ap.mime);
debug!("get-masterkey option is {:?}", ap.get_masterkey);
match ap.version {
None => (), Some(None) => crate::version(ap.output), Some(Some(Version::Check)) => crate::version_check(),
}
if ap.contribute {
crate::contribute();
};
if ap.usage {
crate::usage();
return Ok(());
};
if ap.help {
crate::help();
return Ok(());
};
if ap.manual {
crate::manual();
return Ok(());
};
if ap.readme {
crate::readme().await;
return Ok(());
};
if ap.message.is_empty() && !stdin().is_terminal() {
debug!(
"-m is empty, but there is something piped into stdin. Let's assume '-m -' \
and read and send the information piped in on stdin."
);
ap.message.push("-".to_string());
};
debug!(
"message {:?}, is_terminal() = {:?} (if it not the terminal than it is a pipe on stdin)",
ap.message,
stdin().is_terminal()
);
if !(!ap.login.is_none()
|| ap.whoami
|| ap.bootstrap
|| !ap.verify.is_none()
|| ap.devices
|| !ap.get_room_info.is_empty()
|| ap.rooms
|| ap.invited_rooms
|| ap.joined_rooms
|| ap.left_rooms
|| !ap.room_get_visibility.is_empty()
|| !ap.room_get_state.is_empty()
|| !ap.joined_members.is_empty()
|| !ap.room_resolve_alias.is_empty()
|| ap.get_avatar.is_some()
|| ap.get_avatar_url
|| ap.get_display_name
|| ap.get_profile
|| !ap.media_download.is_empty()
|| !ap.media_mxc_to_http.is_empty()
|| ap.get_masterkey
|| !ap.room_create.is_empty()
|| !ap.room_dm_create.is_empty()
|| !ap.room_leave.is_empty()
|| !ap.room_forget.is_empty()
|| !ap.room_invite.is_empty()
|| !ap.room_join.is_empty()
|| !ap.room_ban.is_empty()
|| !ap.room_unban.is_empty()
|| !ap.room_kick.is_empty()
|| !ap.delete_device.is_empty()
|| ap.set_avatar.is_some()
|| ap.set_avatar_url.is_some()
|| ap.unset_avatar_url
|| ap.set_display_name.is_some()
|| !ap.room_enable_encryption.is_empty()
|| !ap.media_upload.is_empty()
|| !ap.media_delete.is_empty()
|| !ap.message.is_empty()
|| !ap.file.is_empty()
|| ap.listen.is_once()
|| ap.listen.is_forever()
|| ap.listen.is_tail()
|| ap.tail > 0
|| ap.listen.is_all()
|| !ap.logout.is_none())
{
debug!("There are no more actions to take. No need to connect to server. Quitting.");
debug!("Good bye");
return Ok(());
}
let (clientres, credentials) = if !ap.login.is_none() {
match crate::cli_login(&mut ap).await {
Ok((client, credentials)) => (Ok(client), credentials),
Err(ref e) => {
error!(
"Login to server failed or credentials information could not be \
written to disk. Check your arguments and try --login again. \
Reported error is: {:?}",
e
);
return Err(Error::LoginFailed);
}
}
} else if let Ok(credentials) = restore_credentials(&ap) {
(
crate::cli_restore_login(&credentials, &ap).await,
credentials,
)
} else {
error!(
"Credentials file does not exists or cannot be read. \
Consider doing a '--logout' to clean up, then perform a '--login'."
);
return Err(Error::LoginFailed);
};
ap.creds = Some(credentials);
if ap.whoami {
match crate::cli_whoami(&ap) {
Ok(ref _n) => debug!("crate::whoami successful"),
Err(e) => {
error!("Error: crate::whoami reported {}", e);
errcount += 1;
result = Err(e);
}
};
};
convert_to_full_mxc_uris(
&mut ap.media_mxc_to_http,
ap.creds.as_ref().unwrap().homeserver.host_str().unwrap(),
)
.await;
if !ap.media_mxc_to_http.is_empty() {
match crate::cli_media_mxc_to_http(&ap).await {
Ok(ref _n) => debug!("crate::media_mxc_to_http successful"),
Err(e) => {
error!("Error: crate::media_mxc_to_http reported {}", e);
errcount += 1;
result = Err(e);
}
};
};
match clientres {
Ok(client) => {
debug!("A valid client connection has been established with server.");
let default_room =
get_room_default_from_credentials(&client, ap.creds.as_ref().unwrap()).await;
let creds = ap.creds.clone().unwrap();
let hostname = creds.homeserver.host_str().unwrap(); set_rooms(&mut ap, &default_room); set_users(&mut ap);
replace_minus_with_default_room(&mut ap.room_leave, &default_room); convert_to_full_room_ids(&client, &mut ap.room_leave, hostname).await;
replace_minus_with_default_room(&mut ap.room_forget, &default_room); convert_to_full_room_ids(&client, &mut ap.room_forget, hostname).await;
convert_to_full_room_aliases(&mut ap.room_resolve_alias, hostname);
replace_minus_with_default_room(&mut ap.room_enable_encryption, &default_room); convert_to_full_room_ids(&client, &mut ap.room_enable_encryption, hostname).await;
replace_minus_with_default_room(&mut ap.get_room_info, &default_room); convert_to_full_room_ids(&client, &mut ap.get_room_info, hostname).await;
replace_minus_with_default_room(&mut ap.room_invite, &default_room); convert_to_full_room_ids(&client, &mut ap.room_invite, hostname).await;
replace_minus_with_default_room(&mut ap.room_join, &default_room); convert_to_full_room_ids(&client, &mut ap.room_join, hostname).await;
replace_minus_with_default_room(&mut ap.room_ban, &default_room); convert_to_full_room_ids(&client, &mut ap.room_ban, hostname).await;
replace_minus_with_default_room(&mut ap.room_unban, &default_room); convert_to_full_room_ids(&client, &mut ap.room_unban, hostname).await;
replace_minus_with_default_room(&mut ap.room_kick, &default_room); convert_to_full_room_ids(&client, &mut ap.room_kick, hostname).await;
replace_minus_with_default_room(&mut ap.room_get_visibility, &default_room); replace_star_with_rooms(&client, &mut ap.room_get_visibility); convert_to_full_room_ids(&client, &mut ap.room_get_visibility, hostname).await;
replace_minus_with_default_room(&mut ap.room_get_state, &default_room); replace_star_with_rooms(&client, &mut ap.room_get_state); convert_to_full_room_ids(&client, &mut ap.room_get_state, hostname).await;
replace_minus_with_default_room(&mut ap.joined_members, &default_room); replace_star_with_rooms(&client, &mut ap.joined_members); convert_to_full_room_ids(&client, &mut ap.joined_members, hostname).await;
convert_to_full_user_ids(&mut ap.room_dm_create, hostname);
ap.room_dm_create.retain(|x| !x.trim().is_empty());
convert_to_full_alias_ids(&mut ap.alias, hostname);
ap.alias.retain(|x| !x.trim().is_empty());
convert_to_full_mxc_uris(&mut ap.media_download, hostname).await;
convert_to_full_mxc_uris(&mut ap.media_delete, hostname).await;
if ap.tail > 0 {
if !ap.listen.is_never() && !ap.listen.is_tail() {
warn!(
"Two contradicting listening methods were specified. \
Overwritten with --tail. Will use '--listen tail'. {:?} {}",
ap.listen, ap.tail
)
}
ap.listen = Listen::Tail
}
if ap.bootstrap {
match crate::cli_bootstrap(&client, &mut ap).await {
Ok(ref _n) => debug!("crate::bootstrap successful"),
Err(e) => {
error!("Error: crate::bootstrap reported {}", e);
errcount += 1;
result = Err(e);
}
};
};
if !ap.verify.is_none() {
match crate::cli_verify(&client, &ap).await {
Ok(ref _n) => debug!("crate::verify successful"),
Err(e) => {
error!("Error: crate::verify reported {}", e);
errcount += 1;
result = Err(e);
}
};
};
if ap.devices {
match crate::cli_devices(&client, &ap).await {
Ok(ref _n) => debug!("crate::devices successful"),
Err(e) => {
error!("Error: crate::devices reported {}", e);
errcount += 1;
result = Err(e);
}
};
};
if !ap.get_room_info.is_empty() {
match crate::cli_get_room_info(&client, &ap).await {
Ok(ref _n) => debug!("crate::get_room_info successful"),
Err(e) => {
error!("Error: crate::get_room_info reported {}", e);
errcount += 1;
result = Err(e);
}
};
};
if ap.rooms {
match crate::cli_rooms(&client, &ap).await {
Ok(ref _n) => debug!("crate::rooms successful"),
Err(e) => {
error!("Error: crate::rooms reported {}", e);
errcount += 1;
result = Err(e);
}
};
};
if ap.invited_rooms {
match crate::cli_invited_rooms(&client, &ap).await {
Ok(ref _n) => debug!("crate::invited_rooms successful"),
Err(e) => {
error!("Error: crate::invited_rooms reported {}", e);
errcount += 1;
result = Err(e);
}
};
};
if ap.joined_rooms {
match crate::cli_joined_rooms(&client, &ap).await {
Ok(ref _n) => debug!("crate::joined_rooms successful"),
Err(e) => {
error!("Error: crate::joined_rooms reported {}", e);
errcount += 1;
result = Err(e);
}
};
};
if ap.left_rooms {
match crate::cli_left_rooms(&client, &ap).await {
Ok(ref _n) => debug!("crate::left_rooms successful"),
Err(e) => {
error!("Error: crate::left_rooms reported {}", e);
errcount += 1;
result = Err(e);
}
};
};
if !ap.room_get_visibility.is_empty() {
match crate::cli_room_get_visibility(&client, &ap).await {
Ok(ref _n) => debug!("crate::room_get_visibility successful"),
Err(e) => {
error!("Error: crate::room_get_visibility reported {}", e);
errcount += 1;
result = Err(e);
}
};
};
if !ap.room_get_state.is_empty() {
match crate::cli_room_get_state(&client, &ap).await {
Ok(ref _n) => debug!("crate::room_get_state successful"),
Err(e) => {
error!("Error: crate::room_get_state reported {}", e);
errcount += 1;
result = Err(e);
}
};
};
if !ap.joined_members.is_empty() {
match crate::cli_joined_members(&client, &ap).await {
Ok(ref _n) => debug!("crate::joined_members successful"),
Err(e) => {
error!("Error: crate::joined_members reported {}", e);
errcount += 1;
result = Err(e);
}
};
};
if !ap.room_resolve_alias.is_empty() {
match crate::cli_room_resolve_alias(&client, &ap).await {
Ok(ref _n) => debug!("crate::room_resolve_alias successful"),
Err(e) => {
error!("Error: crate::room_resolve_alias reported {}", e);
errcount += 1;
result = Err(e);
}
};
};
if ap.get_avatar.is_some() {
match crate::cli_get_avatar(&client, &ap).await {
Ok(ref _n) => debug!("crate::get_avatar successful"),
Err(e) => {
error!("Error: crate::get_avatar reported {}", e);
errcount += 1;
result = Err(e);
}
};
};
if ap.get_avatar_url {
match crate::cli_get_avatar_url(&client, &ap).await {
Ok(ref _n) => debug!("crate::get_avatar_url successful"),
Err(e) => {
error!("Error: crate::get_avatar_url reported {}", e);
errcount += 1;
result = Err(e);
}
};
};
if ap.get_display_name {
match crate::cli_get_display_name(&client, &ap).await {
Ok(ref _n) => debug!("crate::get_display_name successful"),
Err(e) => {
error!("Error: crate::get_display_name reported {}", e);
errcount += 1;
result = Err(e);
}
};
};
if ap.get_profile {
match crate::cli_get_profile(&client, &ap).await {
Ok(ref _n) => debug!("crate::get_profile successful"),
Err(e) => {
error!("Error: crate::get_profile reported {}", e);
errcount += 1;
result = Err(e);
}
};
};
if ap.get_masterkey {
match crate::cli_get_masterkey(&client, &ap).await {
Ok(ref _n) => debug!("crate::get_masterkey successful"),
Err(e) => {
error!("Error: crate::get_masterkey reported {}", e);
errcount += 1;
result = Err(e);
}
};
};
if !ap.media_download.is_empty() {
match crate::cli_media_download(&client, &ap).await {
Ok(ref _n) => debug!("crate::media_download successful"),
Err(e) => {
error!("Error: crate::media_download reported {}", e);
errcount += 1;
result = Err(e);
}
};
};
if !ap.room_create.is_empty() {
match crate::cli_room_create(&client, &ap).await {
Ok(ref _n) => debug!("crate::room_create successful"),
Err(e) => {
error!("Error: crate::room_create reported {}", e);
errcount += 1;
result = Err(e);
}
};
};
if !ap.room_dm_create.is_empty() {
match crate::cli_room_dm_create(&client, &ap).await {
Ok(ref _n) => debug!("crate::room_dm_create successful"),
Err(e) => {
error!("Error: crate::room_dm_create reported {}", e);
errcount += 1;
result = Err(e);
}
};
};
if !ap.room_leave.is_empty() {
error!(
"There is a bug in the matrix-sdk library and hence this is not working \
properly at the moment. It will start working once matrix-sdk v0.7 is released."
);
match crate::cli_room_leave(&client, &ap).await {
Ok(ref _n) => debug!("crate::room_leave successful"),
Err(e) => {
error!("Error: crate::room_leave reported {}", e);
errcount += 1;
result = Err(e);
}
};
};
if !ap.room_forget.is_empty() {
error!(
"There is a bug in the matrix-sdk library and hence this is not working \
properly at the moment. It might start working once matrix-sdk v0.7 is released."
);
match crate::cli_room_forget(&client, &ap).await {
Ok(ref _n) => debug!("crate::room_forget successful"),
Err(e) => {
error!("Error: crate::room_forget reported {}", e);
errcount += 1;
result = Err(e);
}
};
};
if !ap.room_invite.is_empty() {
match crate::cli_room_invite(&client, &ap).await {
Ok(ref _n) => debug!("crate::room_invite successful"),
Err(e) => {
error!("Error: crate::room_invite reported {}", e);
errcount += 1;
result = Err(e);
}
};
};
if !ap.room_join.is_empty() {
match crate::cli_room_join(&client, &ap).await {
Ok(ref _n) => debug!("crate::room_join successful"),
Err(e) => {
error!("Error: crate::room_join reported {}", e);
errcount += 1;
result = Err(e);
}
};
};
if !ap.room_ban.is_empty() {
match crate::cli_room_ban(&client, &ap).await {
Ok(ref _n) => debug!("crate::room_ban successful"),
Err(e) => {
error!("Error: crate::room_ban reported {}", e);
errcount += 1;
result = Err(e);
}
};
};
if !ap.room_unban.is_empty() {
match crate::cli_room_unban(&client, &ap).await {
Ok(ref _n) => debug!("crate::room_unban successful"),
Err(e) => {
error!("Error: crate::room_unban reported {}", e);
errcount += 1;
result = Err(e);
}
};
};
if !ap.room_kick.is_empty() {
match crate::cli_room_kick(&client, &ap).await {
Ok(ref _n) => debug!("crate::room_kick successful"),
Err(e) => {
error!("Error: crate::room_kick reported {}", e);
errcount += 1;
result = Err(e);
}
};
};
if !ap.delete_device.is_empty() {
match crate::cli_delete_device(&client, &mut ap).await {
Ok(ref _n) => debug!("crate::delete_device successful"),
Err(e) => {
error!("Error: crate::delete_device reported {}", e);
errcount += 1;
result = Err(e);
}
};
};
if ap.set_avatar.is_some() {
match crate::cli_set_avatar(&client, &ap).await {
Ok(ref _n) => debug!("crate::set_avatar successful"),
Err(e) => {
error!("Error: crate::set_avatar reported {}", e);
errcount += 1;
result = Err(e);
}
};
};
if ap.set_avatar_url.is_some() {
match crate::cli_set_avatar_url(&client, &ap).await {
Ok(ref _n) => debug!("crate::set_avatar_url successful"),
Err(e) => {
error!("Error: crate::set_avatar_url reported {}", e);
errcount += 1;
result = Err(e);
}
};
};
if ap.unset_avatar_url {
match crate::cli_unset_avatar_url(&client, &ap).await {
Ok(ref _n) => debug!("crate::set_avatar_url successful"),
Err(e) => {
error!("Error: crate::set_avatar_url reported {}", e);
errcount += 1;
result = Err(e);
}
};
};
if ap.set_display_name.is_some() {
match crate::cli_set_display_name(&client, &ap).await {
Ok(ref _n) => debug!("crate::set_display_name successful"),
Err(e) => {
error!("Error: crate::set_display_name reported {}", e);
errcount += 1;
result = Err(e);
}
};
};
if !ap.room_enable_encryption.is_empty() {
match crate::cli_room_enable_encryption(&client, &ap).await {
Ok(ref _n) => debug!("crate::room_enable_encryption successful"),
Err(e) => {
error!("Error: crate::room_enable_encryption reported {}", e);
errcount += 1;
result = Err(e);
}
};
};
if !ap.media_upload.is_empty() {
match crate::cli_media_upload(&client, &ap).await {
Ok(ref _n) => debug!("crate::media_upload successful"),
Err(e) => {
error!("Error: crate::media_upload reported {}", e);
errcount += 1;
result = Err(e);
}
};
};
if !ap.media_delete.is_empty() {
match crate::cli_media_delete(&client, &ap).await {
Ok(ref _n) => debug!("crate::media_delete successful"),
Err(e) => {
error!("Error: crate::media_delete reported {}", e);
errcount += 1;
result = Err(e);
}
};
};
if !ap.message.is_empty() {
match crate::cli_message(&client, &ap).await {
Ok(ref _n) => debug!("crate::message successful"),
Err(e) => {
error!("Error: crate::message reported {}", e);
errcount += 1;
result = Err(e);
}
};
};
if !ap.file.is_empty() {
match crate::cli_file(&client, &ap).await {
Ok(ref _n) => debug!("crate::file successful"),
Err(e) => {
error!("Error: crate::file reported {}", e);
errcount += 1;
result = Err(e);
}
};
};
if ap.listen.is_once() {
match crate::cli_listen_once(&client, &ap).await {
Ok(ref _n) => debug!("crate::listen_once successful"),
Err(e) => {
error!("Error: crate::listen_once reported {}", e);
errcount += 1;
result = Err(e);
}
};
};
if ap.listen.is_forever() {
match crate::cli_listen_forever(&client, &ap).await {
Ok(ref _n) => debug!("crate::listen_forever successful"),
Err(e) => {
error!("Error: crate::listen_forever reported {}", e);
errcount += 1;
result = Err(e);
}
};
};
if ap.listen.is_tail() {
match crate::cli_listen_tail(&client, &ap).await {
Ok(ref _n) => debug!("crate::listen_tail successful"),
Err(e) => {
error!("Error: crate::listen_tail reported {}", e);
errcount += 1;
result = Err(e);
}
};
};
if ap.listen.is_all() {
match crate::cli_listen_all(&client, &ap).await {
Ok(ref _n) => debug!("crate::listen_all successful"),
Err(e) => {
error!("Error: crate::listen_all reported {}", e);
errcount += 1;
result = Err(e);
}
};
};
if !ap.logout.is_none() {
match crate::cli_logout(&client, &mut ap).await {
Ok(ref _n) => debug!("crate::logout successful"),
Err(e) => {
error!("Error: crate::logout reported {}", e);
errcount += 1;
result = Err(e);
}
};
}
} Err(e) => {
info!(
"Most operations will be skipped because you don't have a valid client connection."
);
error!("Error: {}", e);
errcount += 1;
result = Err(e);
if !ap.logout.is_none() {
match logout_local(&ap) {
Ok(ref _n) => debug!("crate::logout_local successful"),
Err(e) => {
error!("Error: crate::logout_local reported {}", e);
errcount += 1;
result = Err(e);
}
};
};
} } let plural = if errcount == 1 { "" } else { "s" };
if errcount > 0 {
error!("Encountered {} error{}.", errcount, plural);
} else {
debug!("Encountered {} error{}.", errcount, plural);
}
debug!("Good bye");
result
}
#[cfg(test)]
mod tests {
use super::*;
macro_rules! aw {
($e:expr) => {
tokio_test::block_on($e)
};
}
#[test]
fn test_usage() {
assert_eq!(usage(), ());
}
#[test]
fn test_help() {
assert_eq!(help(), ());
}
#[test]
fn test_manual() {
assert_eq!(manual(), ());
}
#[test]
fn test_readme() {
assert_eq!(aw!(readme()), ());
}
#[test]
fn test_version() {
assert_eq!(version(Output::Text), ());
assert_eq!(version(Output::Json), ());
}
#[test]
fn test_version_check() {
assert_eq!(version_check(), ());
}
#[test]
fn test_contribute() {
assert_eq!(contribute(), ());
}
}