|
|
|
@ -0,0 +1,449 @@
@@ -0,0 +1,449 @@
|
|
|
|
|
//! Experimental support for WhatsApp.
|
|
|
|
|
|
|
|
|
|
use whatsappweb::connection::{WhatsappWebConnection, WhatsappWebHandler}; |
|
|
|
|
use whatsappweb::connection::State as WaState; |
|
|
|
|
use whatsappweb::Jid; |
|
|
|
|
use whatsappweb::Contact as WaContact; |
|
|
|
|
use whatsappweb::Chat as WaChat; |
|
|
|
|
use whatsappweb::GroupMetadata; |
|
|
|
|
use whatsappweb::connection::UserData as WaUserData; |
|
|
|
|
use whatsappweb::connection::PersistentSession as WaPersistentSession; |
|
|
|
|
use whatsappweb::connection::DisconnectReason as WaDisconnectReason; |
|
|
|
|
use whatsappweb::message::ChatMessage as WaMessage; |
|
|
|
|
use huawei_modem::pdu::PduAddress; |
|
|
|
|
use futures::sync::mpsc::{self, UnboundedSender, UnboundedReceiver}; |
|
|
|
|
use std::collections::HashMap; |
|
|
|
|
use store::Store; |
|
|
|
|
use std::sync::Arc; |
|
|
|
|
use comm::{WhatsappCommand, ContactFactoryCommand, ControlBotCommand, InitParameters}; |
|
|
|
|
use util::{self, Result}; |
|
|
|
|
use image::Luma; |
|
|
|
|
use qrcode::QrCode; |
|
|
|
|
use futures::{Future, Async, Poll, Stream}; |
|
|
|
|
use failure::Error; |
|
|
|
|
|
|
|
|
|
struct WhatsappHandler { |
|
|
|
|
tx: Arc<UnboundedSender<WhatsappCommand>> |
|
|
|
|
} |
|
|
|
|
impl WhatsappWebHandler for WhatsappHandler { |
|
|
|
|
fn on_state_changed(&self, _: &WhatsappWebConnection<Self>, state: WaState) { |
|
|
|
|
self.tx.unbounded_send(WhatsappCommand::StateChanged(state)) |
|
|
|
|
.unwrap(); |
|
|
|
|
} |
|
|
|
|
fn on_user_data_changed(&self, _: &WhatsappWebConnection<Self>, user_data: WaUserData) { |
|
|
|
|
self.tx.unbounded_send(WhatsappCommand::UserDataChanged(user_data)) |
|
|
|
|
.unwrap(); |
|
|
|
|
} |
|
|
|
|
fn on_persistent_session_data_changed(&self, ps: WaPersistentSession) { |
|
|
|
|
self.tx.unbounded_send(WhatsappCommand::PersistentChanged(ps)) |
|
|
|
|
.unwrap(); |
|
|
|
|
} |
|
|
|
|
fn on_disconnect(&self, reason: WaDisconnectReason) { |
|
|
|
|
self.tx.unbounded_send(WhatsappCommand::Disconnect(reason)) |
|
|
|
|
.unwrap(); |
|
|
|
|
} |
|
|
|
|
fn on_message(&self, _: &WhatsappWebConnection<Self>, new: bool, message: Box<WaMessage>) { |
|
|
|
|
self.tx.unbounded_send(WhatsappCommand::Message(new, message)) |
|
|
|
|
.unwrap(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
pub struct WhatsappManager { |
|
|
|
|
conn: Option<WhatsappWebConnection<WhatsappHandler>>, |
|
|
|
|
rx: UnboundedReceiver<WhatsappCommand>, |
|
|
|
|
wa_tx: Arc<UnboundedSender<WhatsappCommand>>, |
|
|
|
|
cf_tx: UnboundedSender<ContactFactoryCommand>, |
|
|
|
|
cb_tx: UnboundedSender<ControlBotCommand>, |
|
|
|
|
contacts: HashMap<Jid, WaContact>, |
|
|
|
|
chats: HashMap<Jid, WaChat>, |
|
|
|
|
groups: HashMap<Jid, GroupMetadata>, |
|
|
|
|
state: WaState, |
|
|
|
|
connected: bool, |
|
|
|
|
store: Store, |
|
|
|
|
qr_path: String |
|
|
|
|
} |
|
|
|
|
impl Future for WhatsappManager { |
|
|
|
|
type Item = (); |
|
|
|
|
type Error = Error; |
|
|
|
|
|
|
|
|
|
fn poll(&mut self) -> Poll<(), Error> { |
|
|
|
|
while let Async::Ready(com) = self.rx.poll().unwrap() { |
|
|
|
|
let com = com.ok_or(format_err!("whatsappmanager rx died"))?; |
|
|
|
|
self.handle_int_rx(com)?; |
|
|
|
|
} |
|
|
|
|
Ok(Async::NotReady) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
impl WhatsappManager { |
|
|
|
|
pub fn new(p: InitParameters) -> Self { |
|
|
|
|
let store = p.store; |
|
|
|
|
let (wa_tx, rx) = mpsc::unbounded(); |
|
|
|
|
let cf_tx = p.cm.cf_tx.clone(); |
|
|
|
|
let cb_tx = p.cm.cb_tx.clone(); |
|
|
|
|
let qr_path = p.cfg.qr_path.clone().unwrap_or("/tmp/wa_qr.png".into()); |
|
|
|
|
wa_tx.unbounded_send(WhatsappCommand::LogonIfSaved) |
|
|
|
|
.unwrap(); |
|
|
|
|
Self { |
|
|
|
|
conn: None, |
|
|
|
|
contacts: HashMap::new(), |
|
|
|
|
chats: HashMap::new(), |
|
|
|
|
groups: HashMap::new(), |
|
|
|
|
state: WaState::Uninitialized, |
|
|
|
|
connected: false, |
|
|
|
|
wa_tx: Arc::new(wa_tx), |
|
|
|
|
rx, cf_tx, cb_tx, qr_path, store |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
fn handle_int_rx(&mut self, c: WhatsappCommand) -> Result<()> { |
|
|
|
|
use self::WhatsappCommand::*; |
|
|
|
|
|
|
|
|
|
match c { |
|
|
|
|
StartRegistration => self.start_registration()?, |
|
|
|
|
LogonIfSaved => self.logon_if_saved()?, |
|
|
|
|
QrCode(qr) => self.on_qr(qr)?, |
|
|
|
|
SendGroupMessage(to, cont) => self.send_group_message(to, cont)?, |
|
|
|
|
SendDirectMessage(to, cont) => self.send_direct_message(to, cont)?, |
|
|
|
|
GroupAssociate(jid, to) => self.group_associate(jid, to)?, |
|
|
|
|
GroupList => self.group_list()?, |
|
|
|
|
GroupRemove(grp) => self.group_remove(grp)?, |
|
|
|
|
StateChanged(was) => self.on_state_changed(was), |
|
|
|
|
UserDataChanged(wau) => self.on_user_data_changed(wau), |
|
|
|
|
PersistentChanged(wap) => self.on_persistent_session_data_changed(wap)?, |
|
|
|
|
Disconnect(war) => self.on_disconnect(war), |
|
|
|
|
Message(new, msg) => { |
|
|
|
|
if new { |
|
|
|
|
self.on_message(msg)?; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
Ok(()) |
|
|
|
|
} |
|
|
|
|
fn logon_if_saved(&mut self) -> Result<()> { |
|
|
|
|
use whatsappweb::connection; |
|
|
|
|
|
|
|
|
|
if let Some(wap) = self.store.get_wa_persistence_opt()? { |
|
|
|
|
info!("Logging on to WhatsApp Web using stored persistence data"); |
|
|
|
|
let tx = self.wa_tx.clone(); |
|
|
|
|
let (conn, _) = connection::with_persistent_session(wap, WhatsappHandler { tx });
|
|
|
|
|
self.conn = Some(conn); |
|
|
|
|
} |
|
|
|
|
Ok(()) |
|
|
|
|
} |
|
|
|
|
fn start_registration(&mut self) -> Result<()> { |
|
|
|
|
use whatsappweb::connection; |
|
|
|
|
|
|
|
|
|
info!("Beginning WhatsApp Web registration process"); |
|
|
|
|
let tx = self.wa_tx.clone(); |
|
|
|
|
let tx2 = self.wa_tx.clone(); |
|
|
|
|
let (conn, _) = connection::new(move |qr| { |
|
|
|
|
tx2.unbounded_send(WhatsappCommand::QrCode(qr)) |
|
|
|
|
.unwrap() |
|
|
|
|
}, WhatsappHandler { tx }); |
|
|
|
|
self.conn = Some(conn); |
|
|
|
|
Ok(()) |
|
|
|
|
} |
|
|
|
|
fn cb_respond(&mut self, s: String) { |
|
|
|
|
self.cb_tx.unbounded_send(ControlBotCommand::CommandResponse(s)) |
|
|
|
|
.unwrap(); |
|
|
|
|
} |
|
|
|
|
fn on_qr(&mut self, qr: QrCode) -> Result<()> { |
|
|
|
|
info!("Processing registration QR code..."); |
|
|
|
|
qr.render::<Luma<u8>>() |
|
|
|
|
.module_dimensions(10, 10) |
|
|
|
|
.build() |
|
|
|
|
.save(&self.qr_path)?; |
|
|
|
|
let qrn = format!("Scan the QR code saved at {} to log in!", self.qr_path); |
|
|
|
|
self.cb_respond(qrn); |
|
|
|
|
self.cb_respond(format!("NB: The code is only valid for a few seconds, so scan quickly!")); |
|
|
|
|
Ok(()) |
|
|
|
|
} |
|
|
|
|
fn send_direct_message(&mut self, addr: PduAddress, content: String) -> Result<()> { |
|
|
|
|
use whatsappweb::message::ChatMessageContent; |
|
|
|
|
|
|
|
|
|
debug!("Sending direct message to {}...", addr); |
|
|
|
|
trace!("Message contents: {}", content); |
|
|
|
|
if self.conn.is_none() || !self.connected { |
|
|
|
|
warn!("Tried to send WA message to {} while disconnected!", addr); |
|
|
|
|
self.cb_tx.unbounded_send(ControlBotCommand::ReportFailure(format!("Failed to send to WA contact {}: disconnected from server", addr))) |
|
|
|
|
.unwrap(); |
|
|
|
|
return Ok(()); |
|
|
|
|
} |
|
|
|
|
match Jid::from_phone_number(format!("{}", addr)) { |
|
|
|
|
Ok(jid) => { |
|
|
|
|
let content = ChatMessageContent::Text(content); |
|
|
|
|
self.conn.as_mut().unwrap() |
|
|
|
|
.send_message(content, jid); |
|
|
|
|
debug!("WA direct message sent (probably)"); |
|
|
|
|
}, |
|
|
|
|
Err(e) => { |
|
|
|
|
warn!("Couldn't send WA message to {}: {}", addr, e); |
|
|
|
|
self.cb_tx.unbounded_send(ControlBotCommand::ReportFailure(format!("Failed to send to WA contact {}: {}", addr, e))) |
|
|
|
|
.unwrap(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
Ok(()) |
|
|
|
|
} |
|
|
|
|
fn send_group_message(&mut self, chan: String, content: String) -> Result<()> { |
|
|
|
|
use whatsappweb::message::ChatMessageContent; |
|
|
|
|
|
|
|
|
|
debug!("Sending message to group with chan {}...", chan); |
|
|
|
|
trace!("Message contents: {}", content); |
|
|
|
|
if self.conn.is_none() || !self.connected { |
|
|
|
|
warn!("Tried to send WA message to group {} while disconnected!", chan); |
|
|
|
|
self.cb_tx.unbounded_send(ControlBotCommand::ReportFailure(format!("Failed to send to group {}: disconnected from server", chan))) |
|
|
|
|
.unwrap(); |
|
|
|
|
return Ok(()); |
|
|
|
|
} |
|
|
|
|
if let Some(grp) = self.store.get_group_by_chan_opt(&chan)? { |
|
|
|
|
let jid = grp.jid.parse().expect("bad jid in DB"); |
|
|
|
|
let content = ChatMessageContent::Text(content); |
|
|
|
|
self.conn.as_mut().unwrap() |
|
|
|
|
.send_message(content, jid); |
|
|
|
|
debug!("WA group message sent (probably)"); |
|
|
|
|
} |
|
|
|
|
else { |
|
|
|
|
error!("Tried to send WA message to nonexistent group {}", chan); |
|
|
|
|
} |
|
|
|
|
Ok(()) |
|
|
|
|
} |
|
|
|
|
fn on_message(&mut self, msg: Box<WaMessage>) -> Result<()> { |
|
|
|
|
use whatsappweb::message::{Direction, Peer, ChatMessageContent}; |
|
|
|
|
|
|
|
|
|
trace!("processing WA message: {:?}", msg); |
|
|
|
|
let msg = *msg; // otherwise stupid borrowck gets angry, because Box
|
|
|
|
|
let WaMessage { direction, content, .. } = msg; |
|
|
|
|
if let Direction::Receiving(peer) = direction { |
|
|
|
|
let (from, group) = match peer { |
|
|
|
|
Peer::Individual(j) => (j, None), |
|
|
|
|
Peer::Group { group, participant } => (participant, Some(group)) |
|
|
|
|
}; |
|
|
|
|
let group = match group { |
|
|
|
|
Some(gid) => { |
|
|
|
|
if let Some(grp) = self.store.get_group_by_jid_opt(&gid)? { |
|
|
|
|
Some(grp.id) |
|
|
|
|
} |
|
|
|
|
else { |
|
|
|
|
info!("Received message for unbridged group {}, ignoring...", gid.to_string()); |
|
|
|
|
return Ok(()); |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
None => None |
|
|
|
|
}; |
|
|
|
|
let text = match content { |
|
|
|
|
ChatMessageContent::Text(s) => s, |
|
|
|
|
x => format!("[unimplemented message type: {:?}]", x) |
|
|
|
|
}; |
|
|
|
|
if let Some(addr) = util::jid_to_address(&from) { |
|
|
|
|
self.store.store_plain_message(&addr, &text, group)?; |
|
|
|
|
self.cf_tx.unbounded_send(ContactFactoryCommand::ProcessMessages) |
|
|
|
|
.unwrap(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
Ok(()) |
|
|
|
|
} |
|
|
|
|
fn group_list(&mut self) -> Result<()> { |
|
|
|
|
let mut list = vec![]; |
|