Compare commits
No commits in common. "master" and "beta" have entirely different histories.
1350
Cargo.lock
generated
1350
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
11
Cargo.toml
11
Cargo.toml
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "sendxmpp"
|
name = "sendxmpp"
|
||||||
version = "3.0.1"
|
version = "2.0.0"
|
||||||
authors = ["moparisthebest <admin@moparisthebest.com>"]
|
authors = ["moparisthebest <admin@moparisthebest.com>"]
|
||||||
|
|
||||||
description = "Send XMPP messages from the command line."
|
description = "Send XMPP messages from the command line."
|
||||||
@ -25,10 +25,11 @@ serde_derive = "1.0"
|
|||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
gumdrop = "0.8.0"
|
gumdrop = "0.8.0"
|
||||||
gumdrop_derive = "0.8.0"
|
gumdrop_derive = "0.8.0"
|
||||||
dirs = "4.0.0"
|
dirs = "3.0.1"
|
||||||
tokio-xmpp = { version = "3.2.0", default-features = false, features = ["tls-rust"] }
|
tokio-xmpp = "3.0.0"
|
||||||
tokio = { version = "1", features = ["net", "rt", "rt-multi-thread", "macros", "io-util", "io-std"] }
|
tokio = { version = "1", features = ["net", "rt", "rt-multi-thread", "macros", "io-util", "io-std"] }
|
||||||
xmpp-parsers = "0.19"
|
tokio-tls = { package = "tokio-native-tls", version = "0.3" }
|
||||||
|
xmpp-parsers = "0.18"
|
||||||
die = "0.2.0"
|
die = "0.2.0"
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
env_logger = "0.9"
|
|
||||||
|
@ -18,10 +18,6 @@ Optional arguments:
|
|||||||
-c, --config CONFIG path to config file. default: ~/.config/sendxmpp.toml with fallback to /etc/sendxmpp/sendxmpp.toml
|
-c, --config CONFIG path to config file. default: ~/.config/sendxmpp.toml with fallback to /etc/sendxmpp/sendxmpp.toml
|
||||||
-e, --force-pgp Force OpenPGP encryption for all recipients
|
-e, --force-pgp Force OpenPGP encryption for all recipients
|
||||||
-a, --attempt-pgp Attempt OpenPGP encryption for all recipients
|
-a, --attempt-pgp Attempt OpenPGP encryption for all recipients
|
||||||
-r, --raw Send raw XML stream, cannot be used with recipients or PGP
|
|
||||||
-p, --presence Send a <presence/> after connecting before sending messages, required for receiving for --raw
|
|
||||||
-m, --muc Recipients are Multi-User Chats
|
|
||||||
-n, --nick NICK Nickname to use in Multi-User Chats
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Usage examples:
|
Usage examples:
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
# jid and password exactly like this
|
# jid and password exactly like this, nothing else
|
||||||
|
|
||||||
jid = "jid@example.org"
|
jid = "jid@example.org"
|
||||||
password = "sOmePa55W0rD"
|
password = "sOmePa55W0rD"
|
||||||
# nick = "foobar" # optional nick for Multi-User Chat
|
|
||||||
|
72
src/main.rs
72
src/main.rs
@ -3,20 +3,19 @@ use std::fs::File;
|
|||||||
use std::io::{stdin, Read, Write};
|
use std::io::{stdin, Read, Write};
|
||||||
use std::iter::Iterator;
|
use std::iter::Iterator;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
use die::{die, Die};
|
use die::{die, Die};
|
||||||
use gumdrop::Options;
|
use gumdrop::Options;
|
||||||
use serde_derive::Deserialize;
|
use serde_derive::Deserialize;
|
||||||
|
|
||||||
use std::process::{Command, Stdio};
|
use std::process::{Command, Stdio};
|
||||||
use tokio_xmpp::{SimpleClient as Client};
|
use tokio_xmpp::{xmpp_stream, SimpleClient as Client};
|
||||||
use xmpp_parsers::message::{Body, Message, MessageType};
|
use xmpp_parsers::message::{Body, Message};
|
||||||
use xmpp_parsers::muc::Muc;
|
|
||||||
use xmpp_parsers::presence::{Presence, Show as PresenceShow, Type as PresenceType};
|
use xmpp_parsers::presence::{Presence, Show as PresenceShow, Type as PresenceType};
|
||||||
use xmpp_parsers::{BareJid, Element, FullJid, Jid};
|
use xmpp_parsers::{Element, Jid};
|
||||||
|
|
||||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||||
|
use tokio_tls::TlsStream;
|
||||||
|
|
||||||
use anyhow::{anyhow, bail, Result};
|
use anyhow::{anyhow, bail, Result};
|
||||||
|
|
||||||
@ -24,7 +23,6 @@ use anyhow::{anyhow, bail, Result};
|
|||||||
struct Config {
|
struct Config {
|
||||||
jid: String,
|
jid: String,
|
||||||
password: String,
|
password: String,
|
||||||
nick: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_cfg<P: AsRef<Path>>(path: P) -> Result<Config> {
|
fn parse_cfg<P: AsRef<Path>>(path: P) -> Result<Config> {
|
||||||
@ -56,18 +54,10 @@ struct MyOptions {
|
|||||||
|
|
||||||
#[options(help = "Send a <presence/> after connecting before sending messages, required for receiving for --raw")]
|
#[options(help = "Send a <presence/> after connecting before sending messages, required for receiving for --raw")]
|
||||||
presence: bool,
|
presence: bool,
|
||||||
|
|
||||||
#[options(help = "Recipients are Multi-User Chats")]
|
|
||||||
muc: bool,
|
|
||||||
|
|
||||||
#[options(help = "Nickname to use in Multi-User Chats")]
|
|
||||||
nick: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
env_logger::init();
|
|
||||||
|
|
||||||
let args: Vec<String> = args().collect();
|
let args: Vec<String> = args().collect();
|
||||||
|
|
||||||
// Remember to skip the first argument. That's the program name.
|
// Remember to skip the first argument. That's the program name.
|
||||||
@ -89,19 +79,10 @@ async fn main() {
|
|||||||
if !recipients.is_empty() {
|
if !recipients.is_empty() {
|
||||||
die!("--raw is incompatible with recipients");
|
die!("--raw is incompatible with recipients");
|
||||||
}
|
}
|
||||||
if opts.muc {
|
|
||||||
die!("--raw is incompatible with --muc");
|
|
||||||
}
|
|
||||||
} else if recipients.is_empty() {
|
} else if recipients.is_empty() {
|
||||||
die!("no recipients specified!");
|
die!("no recipients specified!");
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.muc {
|
|
||||||
if opts.force_pgp || opts.attempt_pgp {
|
|
||||||
die!("--force-pgp and --attempt-pgp isn't implemented with --muc");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let recipients = &recipients;
|
let recipients = &recipients;
|
||||||
|
|
||||||
let cfg = match opts.config {
|
let cfg = match opts.config {
|
||||||
@ -120,13 +101,19 @@ async fn main() {
|
|||||||
|
|
||||||
// can paste this to test: <message xmlns="jabber:client" to="travis@burtrum.org" type="chat"><body>woot</body></message>
|
// can paste this to test: <message xmlns="jabber:client" to="travis@burtrum.org" type="chat"><body>woot</body></message>
|
||||||
|
|
||||||
let mut open_client = client.into_inner().into_inner();
|
pub struct OpenClient {
|
||||||
|
pub stream: xmpp_stream::XMPPStream<TlsStream<tokio::net::TcpStream>>,
|
||||||
|
}
|
||||||
|
let client: OpenClient = unsafe { std::mem::transmute(client) };
|
||||||
|
let mut open_client = client.stream.stream.try_lock().die("could not lock client stream");
|
||||||
|
let open_client = open_client.get_mut();
|
||||||
|
|
||||||
let mut rd_buf = [0u8; 256]; // todo: proper buffer size?
|
let mut rd_buf = [0u8; 256]; // todo: proper buffer size?
|
||||||
let mut stdin_buf = rd_buf.clone();
|
let mut stdin_buf = rd_buf.clone();
|
||||||
|
|
||||||
let mut stdin = tokio::io::stdin();
|
let mut stdin = tokio::io::stdin();
|
||||||
let mut stdout = tokio::io::stdout();
|
let mut stdout = tokio::io::stdout();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
n = open_client.read(&mut rd_buf) => {
|
n = open_client.read(&mut rd_buf) => {
|
||||||
@ -156,10 +143,6 @@ async fn main() {
|
|||||||
let mut data = String::new();
|
let mut data = String::new();
|
||||||
stdin().lock().read_to_string(&mut data).die("error reading from stdin");
|
stdin().lock().read_to_string(&mut data).die("error reading from stdin");
|
||||||
let data = data.trim();
|
let data = data.trim();
|
||||||
if data.is_empty() {
|
|
||||||
// don't send empty stanzas
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut client = Client::new(&cfg.jid, &cfg.password).await.die("could not connect to xmpp server");
|
let mut client = Client::new(&cfg.jid, &cfg.password).await.die("could not connect to xmpp server");
|
||||||
|
|
||||||
@ -168,46 +151,28 @@ async fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for recipient in recipients {
|
for recipient in recipients {
|
||||||
if opts.muc {
|
|
||||||
let nick = opts
|
|
||||||
.nick
|
|
||||||
.clone()
|
|
||||||
.or(cfg.nick.clone())
|
|
||||||
.or_else(|| BareJid::from_str(cfg.jid.as_str()).unwrap().node)
|
|
||||||
.die("couldn't find a nick to use");
|
|
||||||
let participant = match recipient.clone() {
|
|
||||||
Jid::Full(_) => die!("Invalid room address"),
|
|
||||||
Jid::Bare(bare) => bare.with_resource(nick.clone()),
|
|
||||||
};
|
|
||||||
let join = make_join(participant.clone());
|
|
||||||
client.send_stanza(join).await.die("failed to join MUC");
|
|
||||||
|
|
||||||
let reply = make_reply(recipient.clone(), &data, opts.muc);
|
|
||||||
client.send_stanza(reply).await.die("sending message failed");
|
|
||||||
} else {
|
|
||||||
let reply = if opts.force_pgp || opts.attempt_pgp {
|
let reply = if opts.force_pgp || opts.attempt_pgp {
|
||||||
let encrypted = gpg_encrypt(recipient.clone(), &data);
|
let encrypted = gpg_encrypt(recipient.clone(), &data);
|
||||||
if encrypted.is_err() {
|
if encrypted.is_err() {
|
||||||
if opts.force_pgp {
|
if opts.force_pgp {
|
||||||
die!("pgp encryption to jid '{}' failed!", recipient);
|
die!("pgp encryption to jid '{}' failed!", recipient);
|
||||||
} else {
|
} else {
|
||||||
make_reply(recipient.clone(), &data, opts.muc)
|
make_reply(recipient.clone(), &data)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let encrypted = encrypted.unwrap();
|
let encrypted = encrypted.unwrap();
|
||||||
let encrypted = encrypted.trim();
|
let encrypted = encrypted.trim();
|
||||||
let mut reply = make_reply(recipient.clone(), "pgp", opts.muc);
|
let mut reply = make_reply(recipient.clone(), "pgp");
|
||||||
let mut x = Element::bare("x", "jabber:x:encrypted");
|
let mut x = Element::bare("x", "jabber:x:encrypted");
|
||||||
x.append_text_node(encrypted);
|
x.append_text_node(encrypted);
|
||||||
reply.append_child(x);
|
reply.append_child(x);
|
||||||
reply
|
reply
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
make_reply(recipient.clone(), &data, opts.muc)
|
make_reply(recipient.clone(), &data)
|
||||||
};
|
};
|
||||||
client.send_stanza(reply).await.die("sending message failed");
|
client.send_stanza(reply).await.die("sending message failed");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Close client connection
|
// Close client connection
|
||||||
client.end().await.ok(); // ignore errors here, I guess
|
client.end().await.ok(); // ignore errors here, I guess
|
||||||
@ -221,16 +186,9 @@ fn make_presence() -> Element {
|
|||||||
presence.into()
|
presence.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_join(to: FullJid) -> Element {
|
|
||||||
Presence::new(PresenceType::None).with_to(Jid::Full(to)).with_payloads(vec![Muc::new().into()]).into()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Construct a chat <message/>
|
// Construct a chat <message/>
|
||||||
fn make_reply(to: Jid, body: &str, groupchat: bool) -> Element {
|
fn make_reply(to: Jid, body: &str) -> Element {
|
||||||
let mut message = Message::new(Some(to));
|
let mut message = Message::new(Some(to));
|
||||||
if groupchat {
|
|
||||||
message.type_ = MessageType::Groupchat;
|
|
||||||
}
|
|
||||||
message.bodies.insert(String::new(), Body(body.to_owned()));
|
message.bodies.insert(String::new(), Body(body.to_owned()));
|
||||||
message.into()
|
message.into()
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user