Compare commits
8 Commits
raw_newlib
...
master
Author | SHA1 | Date | |
---|---|---|---|
5b36300054 | |||
7aff2080df | |||
294da89d22 | |||
f1aae8e3c2 | |||
c8ce4bb629 | |||
|
3b36852749 | ||
|
06ecb97f4c | ||
|
b643665354 |
1356
Cargo.lock
generated
1356
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
11
Cargo.toml
11
Cargo.toml
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "sendxmpp"
|
||||
version = "2.0.0"
|
||||
version = "3.0.1"
|
||||
authors = ["moparisthebest <admin@moparisthebest.com>"]
|
||||
|
||||
description = "Send XMPP messages from the command line."
|
||||
@ -25,11 +25,10 @@ serde_derive = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
gumdrop = "0.8.0"
|
||||
gumdrop_derive = "0.8.0"
|
||||
dirs = "3.0.1"
|
||||
tokio-xmpp = "3.0.0"
|
||||
dirs = "4.0.0"
|
||||
tokio-xmpp = { version = "3.2.0", default-features = false, features = ["tls-rust"] }
|
||||
tokio = { version = "1", features = ["net", "rt", "rt-multi-thread", "macros", "io-util", "io-std"] }
|
||||
tokio-tls = { package = "tokio-native-tls", version = "0.3" }
|
||||
xmpp-parsers = "0.18"
|
||||
xmpp-parsers = "0.19"
|
||||
die = "0.2.0"
|
||||
anyhow = "1.0"
|
||||
|
||||
env_logger = "0.9"
|
||||
|
@ -18,6 +18,10 @@ Optional arguments:
|
||||
-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
|
||||
-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:
|
||||
|
@ -1,4 +1,5 @@
|
||||
# jid and password exactly like this, nothing else
|
||||
# jid and password exactly like this
|
||||
|
||||
jid = "jid@example.org"
|
||||
password = "sOmePa55W0rD"
|
||||
# nick = "foobar" # optional nick for Multi-User Chat
|
||||
|
102
src/main.rs
102
src/main.rs
@ -3,19 +3,20 @@ use std::fs::File;
|
||||
use std::io::{stdin, Read, Write};
|
||||
use std::iter::Iterator;
|
||||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
|
||||
use die::{die, Die};
|
||||
use gumdrop::Options;
|
||||
use serde_derive::Deserialize;
|
||||
|
||||
use std::process::{Command, Stdio};
|
||||
use tokio_xmpp::{xmpp_stream, SimpleClient as Client};
|
||||
use xmpp_parsers::message::{Body, Message};
|
||||
use tokio_xmpp::{SimpleClient as Client};
|
||||
use xmpp_parsers::message::{Body, Message, MessageType};
|
||||
use xmpp_parsers::muc::Muc;
|
||||
use xmpp_parsers::presence::{Presence, Show as PresenceShow, Type as PresenceType};
|
||||
use xmpp_parsers::{Element, Jid};
|
||||
use xmpp_parsers::{BareJid, Element, FullJid, Jid};
|
||||
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use tokio_tls::TlsStream;
|
||||
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
|
||||
@ -23,6 +24,7 @@ use anyhow::{anyhow, bail, Result};
|
||||
struct Config {
|
||||
jid: String,
|
||||
password: String,
|
||||
nick: Option<String>,
|
||||
}
|
||||
|
||||
fn parse_cfg<P: AsRef<Path>>(path: P) -> Result<Config> {
|
||||
@ -54,10 +56,18 @@ struct MyOptions {
|
||||
|
||||
#[options(help = "Send a <presence/> after connecting before sending messages, required for receiving for --raw")]
|
||||
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]
|
||||
async fn main() {
|
||||
env_logger::init();
|
||||
|
||||
let args: Vec<String> = args().collect();
|
||||
|
||||
// Remember to skip the first argument. That's the program name.
|
||||
@ -79,10 +89,19 @@ async fn main() {
|
||||
if !recipients.is_empty() {
|
||||
die!("--raw is incompatible with recipients");
|
||||
}
|
||||
if opts.muc {
|
||||
die!("--raw is incompatible with --muc");
|
||||
}
|
||||
} else if recipients.is_empty() {
|
||||
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 cfg = match opts.config {
|
||||
@ -101,19 +120,13 @@ async fn main() {
|
||||
|
||||
// can paste this to test: <message xmlns="jabber:client" to="travis@burtrum.org" type="chat"><body>woot</body></message>
|
||||
|
||||
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 open_client = client.into_inner().into_inner();
|
||||
|
||||
let mut rd_buf = [0u8; 256]; // todo: proper buffer size?
|
||||
let mut stdin_buf = rd_buf.clone();
|
||||
|
||||
let mut stdin = tokio::io::stdin();
|
||||
let mut stdout = tokio::io::stdout();
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
n = open_client.read(&mut rd_buf) => {
|
||||
@ -143,6 +156,10 @@ async fn main() {
|
||||
let mut data = String::new();
|
||||
stdin().lock().read_to_string(&mut data).die("error reading from stdin");
|
||||
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");
|
||||
|
||||
@ -151,27 +168,45 @@ async fn main() {
|
||||
}
|
||||
|
||||
for recipient in recipients {
|
||||
let reply = if opts.force_pgp || opts.attempt_pgp {
|
||||
let encrypted = gpg_encrypt(recipient.clone(), &data);
|
||||
if encrypted.is_err() {
|
||||
if opts.force_pgp {
|
||||
die!("pgp encryption to jid '{}' failed!", recipient);
|
||||
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 encrypted = gpg_encrypt(recipient.clone(), &data);
|
||||
if encrypted.is_err() {
|
||||
if opts.force_pgp {
|
||||
die!("pgp encryption to jid '{}' failed!", recipient);
|
||||
} else {
|
||||
make_reply(recipient.clone(), &data, opts.muc)
|
||||
}
|
||||
} else {
|
||||
make_reply(recipient.clone(), &data)
|
||||
let encrypted = encrypted.unwrap();
|
||||
let encrypted = encrypted.trim();
|
||||
let mut reply = make_reply(recipient.clone(), "pgp", opts.muc);
|
||||
let mut x = Element::bare("x", "jabber:x:encrypted");
|
||||
x.append_text_node(encrypted);
|
||||
reply.append_child(x);
|
||||
reply
|
||||
}
|
||||
} else {
|
||||
let encrypted = encrypted.unwrap();
|
||||
let encrypted = encrypted.trim();
|
||||
let mut reply = make_reply(recipient.clone(), "pgp");
|
||||
let mut x = Element::bare("x", "jabber:x:encrypted");
|
||||
x.append_text_node(encrypted);
|
||||
reply.append_child(x);
|
||||
reply
|
||||
}
|
||||
} else {
|
||||
make_reply(recipient.clone(), &data)
|
||||
};
|
||||
client.send_stanza(reply).await.die("sending message failed");
|
||||
make_reply(recipient.clone(), &data, opts.muc)
|
||||
};
|
||||
client.send_stanza(reply).await.die("sending message failed");
|
||||
}
|
||||
}
|
||||
|
||||
// Close client connection
|
||||
@ -186,9 +221,16 @@ fn make_presence() -> Element {
|
||||
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/>
|
||||
fn make_reply(to: Jid, body: &str) -> Element {
|
||||
fn make_reply(to: Jid, body: &str, groupchat: bool) -> Element {
|
||||
let mut message = Message::new(Some(to));
|
||||
if groupchat {
|
||||
message.type_ = MessageType::Groupchat;
|
||||
}
|
||||
message.bodies.insert(String::new(), Body(body.to_owned()));
|
||||
message.into()
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user