Browse Source

Add some rudimentary presence tracking options

- If `whatsapp.track_presence` is enabled, the bridge will request
  presence updates for users after you send them a message.
- The `GHOST PRESUB` command was also added, which manually attempts
  to subscribe to presence updates.
master
eta 4 years ago
parent
commit
9d49a7cbad
  1. 7
      src/admin.rs
  2. 4
      src/comm.rs
  3. 4
      src/config.rs
  4. 9
      src/contact_common.rs
  5. 1
      src/contact_factory.rs
  6. 4
      src/control_common.rs
  7. 1
      src/insp_s2s.rs
  8. 63
      src/whatsapp.rs

7
src/admin.rs

@ -16,6 +16,7 @@ macro_rules! extract_command { @@ -16,6 +16,7 @@ macro_rules! extract_command {
pub enum GhostCommand {
ChangeNick(String),
SetWhatsapp(bool),
PresenceSubscribe,
Remove
}
impl GhostCommand {
@ -28,6 +29,9 @@ The following commands are available: @@ -28,6 +29,9 @@ The following commands are available:
Enable or disable WhatsApp mode for this recipient.
\x02REMOVE\x0f \x0307(aliases \x02KILL\x02, \x02DIE\x02)\x0f
Remove this recipient, causing them to disconnect.
\x02PRESUB\x0f
\x02Sub\x0fscribe to updates about the user's \x02pre\x0fsence, if they're using WhatsApp.
This command will usually not be required, and is mainly useful for debugging.
\x02*** End of subcommand help ***\x0f"
}
pub fn parse(inp: &[&str]) -> Option<Self> {
@ -44,6 +48,9 @@ The following commands are available: @@ -44,6 +48,9 @@ The following commands are available:
None
}
},
("presub", _) => {
Some(GhostCommand::PresenceSubscribe)
},
("die", _) | ("kill", _) | ("remove", _) => {
Some(GhostCommand::Remove)
},

4
src/comm.rs

@ -34,6 +34,7 @@ pub enum WhatsappCommand { @@ -34,6 +34,7 @@ pub enum WhatsappCommand {
CheckAcks,
PrintAcks,
MakeContact(PduAddress),
SubscribePresence(PduAddress)
}
#[allow(dead_code)]
pub enum ContactFactoryCommand {
@ -47,7 +48,8 @@ pub enum ContactFactoryCommand { @@ -47,7 +48,8 @@ pub enum ContactFactoryCommand {
DropContactByNick(String),
LoadRecipients,
ForwardCommand(PduAddress, ContactManagerCommand),
ForwardCommandByNick(String, ContactManagerCommand)
ForwardCommandByNick(String, ContactManagerCommand),
SubscribePresenceByNick(String)
}
pub enum ContactManagerCommand {
ProcessMessages,

4
src/config.rs

@ -62,7 +62,9 @@ pub struct WhatsappConfig { @@ -62,7 +62,9 @@ pub struct WhatsappConfig {
#[serde(default)]
pub load_history_messages: Option<u16>,
#[serde(default)]
pub backoff_time_ms: Option<u64>
pub backoff_time_ms: Option<u64>,
#[serde(default)]
pub track_presence: bool
}
#[derive(Deserialize, Debug, Clone)]
pub struct IrcClientConfig {

9
src/contact_common.rs

@ -86,6 +86,15 @@ pub trait ContactManagerManager { @@ -86,6 +86,15 @@ pub trait ContactManagerManager {
self.remove_contact_for(&addr)?;
Ok(())
}
fn subscribe_presence_by_nick(&mut self, nick: String) {
if let Some(a) = self.resolve_nick(&nick) {
self.wa_tx().unbounded_send(WhatsappCommand::SubscribePresence(a))
.unwrap();
}
else {
warn!("Tried to subscribe presence for nonexistent nick {}", nick);
}
}
fn drop_contact_by_nick(&mut self, nick: String) -> Result<()> {
if let Some(a) = self.resolve_nick(&nick) {
self.drop_contact(a)?;

1
src/contact_factory.rs

@ -51,6 +51,7 @@ impl Future for ContactFactory { @@ -51,6 +51,7 @@ impl Future for ContactFactory {
DropContactByNick(nick) => self.drop_contact_by_nick(nick)?,
ForwardCommand(addr, cmd) => self.forward_cmd(&addr, cmd)?,
ForwardCommandByNick(nick, cmd) => self.forward_cmd_by_nick(&nick, cmd)?,
SubscribePresenceByNick(nick) => self.subscribe_presence_by_nick(nick),
ProcessAvatars => {
// FIXME: implement
}

4
src/control_common.rs

@ -51,6 +51,10 @@ pub trait ControlCommon { @@ -51,6 +51,10 @@ pub trait ControlCommon {
SetWhatsapp(n) => {
c = Some(ContactManagerCommand::SetWhatsapp(n));
},
PresenceSubscribe => {
self.cf_tx().unbounded_send(ContactFactoryCommand::SubscribePresenceByNick(nick.clone()))
.unwrap();
},
Remove => {
self.cf_tx().unbounded_send(ContactFactoryCommand::DropContactByNick(nick.clone()))
.unwrap();

1
src/insp_s2s.rs

@ -600,6 +600,7 @@ impl InspLink { @@ -600,6 +600,7 @@ impl InspLink {
},
ForwardCommand(a, cmd) => self.forward_cmd(&a, cmd)?,
ForwardCommandByNick(a, cmd) => self.forward_cmd_by_nick(&a, cmd)?,
SubscribePresenceByNick(nick) => self.subscribe_presence_by_nick(nick),
ProcessAvatars => {
// FIXME: implement
//

63
src/whatsapp.rs

@ -49,6 +49,7 @@ pub struct WhatsappManager { @@ -49,6 +49,7 @@ pub struct WhatsappManager {
cb_tx: UnboundedSender<ControlBotCommand>,
contacts: HashMap<Jid, WaContact>,
chats: HashMap<Jid, WaChat>,
presence_requests: HashMap<Jid, Instant>,
outgoing_messages: HashMap<String, MessageSendStatus>,
ack_warn: u64,
ack_warn_pending: u64,
@ -63,6 +64,7 @@ pub struct WhatsappManager { @@ -63,6 +64,7 @@ pub struct WhatsappManager {
autocreate: Option<String>,
autoupdate_nicks: bool,
mark_read: bool,
track_presence: bool,
our_jid: Option<Jid>,
outbox: VecDeque<WaRequest>
}
@ -129,6 +131,7 @@ impl WhatsappManager { @@ -129,6 +131,7 @@ impl WhatsappManager {
let mark_read = p.cfg.whatsapp.mark_read;
let autoupdate_nicks = p.cfg.whatsapp.autoupdate_nicks;
let backoff_time_ms = p.cfg.whatsapp.backoff_time_ms.unwrap_or(10000);
let track_presence = p.cfg.whatsapp.track_presence;
wa_tx.unbounded_send(WhatsappCommand::LogonIfSaved)
.unwrap();
let wa_tx = Arc::new(wa_tx);
@ -152,6 +155,7 @@ impl WhatsappManager { @@ -152,6 +155,7 @@ impl WhatsappManager {
outgoing_messages: HashMap::new(),
connected: false,
our_jid: None,
presence_requests: HashMap::new(),
ack_warn: ack_warn_ms,
ack_warn_pending: ack_warn_pending_ms,
ack_expiry: ack_expiry_ms,
@ -159,7 +163,7 @@ impl WhatsappManager { @@ -159,7 +163,7 @@ impl WhatsappManager {
outbox: VecDeque::new(),
wa_tx, backlog_start,
rx, cf_tx, cb_tx, qr_path, store, media_path, dl_path, autocreate,
mark_read, autoupdate_nicks
mark_read, autoupdate_nicks, track_presence
}
}
fn handle_int_rx(&mut self, c: WhatsappCommand) -> Result<()> {
@ -177,7 +181,26 @@ impl WhatsappManager { @@ -177,7 +181,26 @@ impl WhatsappManager {
MediaFinished(r) => self.media_finished(r)?,
CheckAcks => self.check_acks()?,
PrintAcks => self.print_acks()?,
MakeContact(a) => self.make_contact(a)?
MakeContact(a) => self.make_contact(a)?,
SubscribePresence(a) => self.subscribe_presence(a)?
}
Ok(())
}
fn subscribe_presence(&mut self, addr: PduAddress) -> Result<()> {
match util::address_to_jid(&addr) {
Ok(from) => {
let recip = self.get_wa_recipient(&from)?;
if self.connected {
self.cb_respond(format!("Subscribing to presence updates from '{}' (jid {})`", recip.nick, from));
self.outbox.push_back(WaRequest::SubscribePresence(from));
}
else {
self.cb_respond("Error subscribing: not connected to WA");
}
},
Err(_) => {
self.cb_respond("Error subscribing: invalid PduAddress");
}
}
Ok(())
}
@ -195,7 +218,6 @@ impl WhatsappManager { @@ -195,7 +218,6 @@ impl WhatsappManager {
Ok(())
}
fn print_acks(&mut self) -> Result<()> {
self.cb_respond("Message receipts:".into());
let now = Utc::now();
let mut lines = vec![];
for (mid, mss) in self.outgoing_messages.iter_mut() {
@ -215,6 +237,11 @@ impl WhatsappManager { @@ -215,6 +237,11 @@ impl WhatsappManager {
summary, mss.destination.to_string(), delta.num_seconds(), al));
lines.push(format!(" (message ID \x11{}\x0f)", mid));
}
if lines.len() == 0 {
self.cb_respond("No outgoing messages.");
return Ok(());
}
self.cb_respond("Message receipts:");
for line in lines {
self.cb_respond(line);
}
@ -310,8 +337,8 @@ impl WhatsappManager { @@ -310,8 +337,8 @@ impl WhatsappManager {
self.conn.connect_new();
Ok(())
}
fn cb_respond(&mut self, s: String) {
self.cb_tx.unbounded_send(ControlBotCommand::CommandResponse(s))
fn cb_respond<T: Into<String>>(&mut self, s: T) {
self.cb_tx.unbounded_send(ControlBotCommand::CommandResponse(s.into()))
.unwrap();
}
fn on_qr(&mut self, qr: QrCode) -> Result<()> {
@ -322,7 +349,7 @@ impl WhatsappManager { @@ -322,7 +349,7 @@ impl WhatsappManager {
.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!"));
self.cb_respond("NB: The code is only valid for a few seconds, so scan quickly!");
Ok(())
}
fn send_message(&mut self, content: ChatMessageContent, jid: Jid) -> Result<()> {
@ -331,7 +358,7 @@ impl WhatsappManager { @@ -331,7 +358,7 @@ impl WhatsappManager {
ack_level: None,
sent_ts: Utc::now(),
content,
destination: jid,
destination: jid.clone(),
alerted: false,
alerted_pending: false
};
@ -343,6 +370,22 @@ impl WhatsappManager { @@ -343,6 +370,22 @@ impl WhatsappManager {
warn!("Failed to store outgoing msgid {}: {}", mid.0, e);
}
self.outgoing_messages.insert(mid.0, mss);
if !jid.is_group && self.track_presence {
let mut update = true;
let now = Instant::now();
if let Some(inst) = self.presence_requests.get(&jid) {
// WhatsApp stops sending you presence updates after about 10 minutes.
// To avoid this, we resubscribe about every 5.
if now.duration_since(*inst) < Duration::new(300, 0) {
update = false;
}
}
if update {
debug!("Requesting presence updates for {}", jid);
self.presence_requests.insert(jid.clone(), now);
self.outbox.push_back(WaRequest::SubscribePresence(jid));
}
}
Ok(())
}
fn send_direct_message(&mut self, addr: PduAddress, content: String) -> Result<()> {
@ -641,10 +684,10 @@ impl WhatsappManager { @@ -641,10 +684,10 @@ impl WhatsappManager {
list.push(format!("- '{}' (jid {}) - {}", gmeta.name.as_ref().map(|x| x as &str).unwrap_or("<unnamed>"), jid, bstatus));
}
if list.len() == 0 {
self.cb_respond("no WhatsApp chats (yet?)".into());
self.cb_respond("no WhatsApp chats (yet?)");
}
else {
self.cb_respond("WhatsApp chats:".into());
self.cb_respond("WhatsApp chats:");
}
for item in list {
self.cb_respond(item);
@ -788,7 +831,7 @@ impl WhatsappManager { @@ -788,7 +831,7 @@ impl WhatsappManager {
}
fn group_associate_handler(&mut self, jid: Jid, chan: String) -> Result<()> {
match self.group_associate(jid, chan, true) {
Ok(_) => self.cb_respond("Group creation successful.".into()),
Ok(_) => self.cb_respond("Group creation successful."),
Err(e) => self.cb_respond(format!("Group creation failed: {}", e))
}
Ok(())

Loading…
Cancel
Save