1use futures::stream::StreamExt;
2use std::env::args;
3use std::process::exit;
4use std::str::FromStr;
5use tokio_xmpp::rustls;
6use tokio_xmpp::{Client, IqRequest, IqResponse};
7use xmpp_parsers::{
8 disco::{DiscoInfoQuery, DiscoInfoResult},
9 jid::{BareJid, Jid},
10 ns,
11 server_info::ServerInfo,
12};
13
14#[tokio::main]
15async fn main() {
16 env_logger::init();
17
18 rustls::crypto::aws_lc_rs::default_provider()
19 .install_default()
20 .expect("failed to install rustls crypto provider");
21
22 let args: Vec<String> = args().collect();
23 if args.len() != 4 {
24 println!("Usage: {} <jid> <password> <target>", args[0]);
25 exit(1);
26 }
27 let jid = BareJid::from_str(&args[1]).expect(&format!("Invalid JID: {}", &args[1]));
28 let password = args[2].clone();
29 let target = Jid::from_str(&args[3]).expect(&format!("Invalid JID: {}", &args[3]));
30
31 // Client instance
32 let mut client = Client::new(jid, password);
33
34 let token = client
35 .send_iq(
36 Some(target),
37 IqRequest::Get(DiscoInfoQuery { node: None }.into()),
38 )
39 .await;
40 tokio::pin!(token);
41
42 // Main loop, processes events
43 loop {
44 tokio::select! {
45 response = &mut token => match response {
46 Ok(IqResponse::Result(Some(payload))) => {
47 if payload.is("query", ns::DISCO_INFO) {
48 if let Ok(disco_info) = DiscoInfoResult::try_from(payload) {
49 for ext in disco_info.extensions {
50 if let Ok(server_info) = ServerInfo::try_from(ext) {
51 print_server_info(server_info);
52 }
53 }
54 }
55 }
56 break;
57 }
58 Ok(IqResponse::Result(None)) => {
59 panic!("disco#info response misses payload!");
60 }
61 Ok(IqResponse::Error(err)) => {
62 panic!("disco#info response is an error: {:?}", err);
63 }
64 Err(err) => {
65 panic!("disco#info request failed to send: {}", err);
66 }
67 },
68 event = client.next() => {
69 let Some(event) = event else {
70 println!("Client terminated");
71 break;
72 };
73 if event.is_online() {
74 println!("Online!");
75 }
76 },
77 }
78 }
79 client.send_end().await.expect("Stream shutdown unclean");
80}
81
82fn convert_field(field: Vec<String>) -> String {
83 field
84 .iter()
85 .fold((field.len(), String::new()), |(l, mut acc), s| {
86 acc.push('<');
87 acc.push_str(&s);
88 acc.push('>');
89 if l > 1 {
90 acc.push(',');
91 acc.push(' ');
92 }
93 (0, acc)
94 })
95 .1
96}
97
98fn print_server_info(server_info: ServerInfo) {
99 if server_info.abuse.len() != 0 {
100 println!("abuse: {}", convert_field(server_info.abuse));
101 }
102 if server_info.admin.len() != 0 {
103 println!("admin: {}", convert_field(server_info.admin));
104 }
105 if server_info.feedback.len() != 0 {
106 println!("feedback: {}", convert_field(server_info.feedback));
107 }
108 if server_info.sales.len() != 0 {
109 println!("sales: {}", convert_field(server_info.sales));
110 }
111 if server_info.security.len() != 0 {
112 println!("security: {}", convert_field(server_info.security));
113 }
114 if server_info.support.len() != 0 {
115 println!("support: {}", convert_field(server_info.support));
116 }
117}