Browse Source

Revamp contact nick handling (+ minor s2c changes)

- Contact nickname handling has been completely revamped, and now
  properly deals with things like nick collisions.
- In addition, the source of nickname information is now recorded,
  meaning that WhatsApp notify / contact name changes now propagate
  to the nickname changing, if the relevant option is enabled.
master
η (eta) 2 years ago
parent
commit
dd50e8c624
  1. 1
      migrations/2019-09-14-115634_nicksrc/down.sql
  2. 1
      migrations/2019-09-14-115634_nicksrc/up.sql
  3. 2
      src/comm.rs
  4. 13
      src/contact.rs
  5. 5
      src/contact_common.rs
  6. 5
      src/contact_factory.rs
  7. 15
      src/control.rs
  8. 31
      src/control_common.rs
  9. 92
      src/insp_s2s.rs
  10. 10
      src/insp_user.rs
  11. 56
      src/irc_s2c.rs
  12. 31
      src/models.rs
  13. 1
      src/schema.rs
  14. 48
      src/store.rs
  15. 66
      src/whatsapp.rs

1
migrations/2019-09-14-115634_nicksrc/down.sql

@ -0,0 +1 @@
ALTER TABLE recipients DROP COLUMN nicksrc;

1
migrations/2019-09-14-115634_nicksrc/up.sql

@ -0,0 +1 @@
ALTER TABLE recipients ADD COLUMN nicksrc INT NOT NULL DEFAULT -1;

2
src/comm.rs

@ -54,7 +54,7 @@ pub enum ContactManagerCommand {
ProcessMessages,
ProcessGroups,
UpdateAway(Option<String>),
ChangeNick(String),
ChangeNick(String, i32),
SetWhatsapp(bool)
}
#[derive(Clone)]

13
src/contact.rs

@ -163,8 +163,8 @@ impl ContactManager {
self.presence = msg;
self.update_away()?;
},
ChangeNick(nick) => {
self.change_nick(nick)?;
ChangeNick(nick, src) => {
self.change_nick(nick, src)?;
},
SetWhatsapp(wam) => {
self.wa_mode = wam;
@ -173,9 +173,9 @@ impl ContactManager {
}
Ok(())
}
fn change_nick(&mut self, nick: String) -> Result<()> {
info!("Contact {} changing nick to {}", self.nick, nick);
self.store.update_recipient_nick(&self.addr, &nick)?;
fn change_nick(&mut self, nick: String, src: i32) -> Result<()> {
debug!("Contact {} changing nick to {}", self.nick, nick);
self.store.update_recipient_nick(&self.addr, &nick, src)?;
self.irc.0.send(Command::NICK(nick))?;
Ok(())
}
@ -271,8 +271,7 @@ impl ContactManager {
pub fn new(recip: Recipient, p: InitParameters<IrcClientConfig>) -> impl Future<Item = Self, Error = Error> {
let store = p.store;
let wa_mode = recip.whatsapp;
let addr = match util::un_normalize_address(&recip.phone_number)
.ok_or(format_err!("invalid num {} in db", recip.phone_number)) {
let addr = match recip.get_addr() {
Ok(r) => r,
Err(e) => return Either::B(futures::future::err(e.into()))
};

5
src/contact_common.rs

@ -4,7 +4,7 @@ use huawei_modem::pdu::PduAddress;
use crate::models::{Recipient, Message};
use crate::store::Store;
use crate::comm::ContactManagerCommand;
use crate::util::{self, Result};
use crate::util::Result;
use futures::sync::mpsc::UnboundedSender;
use crate::comm::{WhatsappCommand, ModemCommand, ControlBotCommand};
@ -19,8 +19,7 @@ pub trait ContactManagerManager {
fn forward_cmd(&mut self, _: &PduAddress, _: ContactManagerCommand) -> Result<()>;
fn resolve_nick(&self, _: &str) -> Option<PduAddress>;
fn setup_recipient(&mut self, recip: Recipient) -> Result<()> {
let addr = util::un_normalize_address(&recip.phone_number)
.ok_or(format_err!("invalid num {} in db", recip.phone_number))?;
let addr = recip.get_addr()?;
debug!("Setting up recipient for {} (nick {})", addr, recip.nick);
if self.has_contact(&addr) {
debug!("Not doing anything; contact already exists");

5
src/contact_factory.rs

@ -9,7 +9,7 @@ use std::collections::{HashMap, HashSet};
use tokio_core::reactor::Handle;
use huawei_modem::pdu::PduAddress;
use crate::contact::ContactManager;
use crate::util::{self, Result};
use crate::util::Result;
use crate::models::Recipient;
use tokio_timer::Interval;
use failure::Error;
@ -201,8 +201,7 @@ impl ContactFactory {
fn process_messages(&mut self) -> Result<()> {
for msg in self.store.get_all_messages()? {
if self.messages_processed.insert(msg.id) {
let addr = util::un_normalize_address(&msg.phone_number)
.ok_or(format_err!("invalid address {} in db", msg.phone_number))?;
let addr = msg.get_addr()?;
if self.contacts_starting.get(&addr).is_some() {
continue;
}

15
src/control.rs

@ -61,14 +61,17 @@ impl Future for ControlBot {
}
}
impl ControlCommon for ControlBot {
fn wa_tx(&mut self) -> &mut UnboundedSender<WhatsappCommand> {
&mut self.wa_tx
fn cf_send(&mut self, c: ContactFactoryCommand) {
self.cf_tx.unbounded_send(c)
.unwrap()
}
fn cf_tx(&mut self) -> &mut UnboundedSender<ContactFactoryCommand> {
&mut self.cf_tx
fn wa_send(&mut self, c: WhatsappCommand) {
self.wa_tx.unbounded_send(c)
.unwrap()
}
fn m_tx(&mut self) -> &mut UnboundedSender<ModemCommand> {
&mut self.m_tx
fn m_send(&mut self, c: ModemCommand) {
self.m_tx.unbounded_send(c)
.unwrap()
}
fn control_response(&mut self, msg: &str) -> Result<()> {
self.irc.0.send_notice(&self.admin, msg)?;

31
src/control_common.rs

@ -1,17 +1,17 @@
//! Common behaviours for the control bot.
use futures::sync::mpsc::UnboundedSender;
use crate::comm::{WhatsappCommand, ContactFactoryCommand, ContactManagerCommand, ModemCommand};
use crate::util::Result;
use crate::models::Recipient;
use crate::admin::{InspCommand, AdminCommand, GhostCommand, GroupCommand, ContactCommand};
use crate::admin::ModemCommand as AdminModemCommand;
use crate::admin::WhatsappCommand as AdminWhatsappCommand;
use crate::models::Message;
pub trait ControlCommon {
fn wa_tx(&mut self) -> &mut UnboundedSender<WhatsappCommand>;
fn cf_tx(&mut self) -> &mut UnboundedSender<ContactFactoryCommand>;
fn m_tx(&mut self) -> &mut UnboundedSender<ModemCommand>;
fn wa_send(&mut self, c: WhatsappCommand);
fn cf_send(&mut self, c: ContactFactoryCommand);
fn m_send(&mut self, c: ModemCommand);
/// Process the InspIRCd-specific command specified.
///
/// Returns `true` if the command was processed, or `false` if it wasn't (i.e. we aren't
@ -46,23 +46,20 @@ pub trait ControlCommon {
match gc {
// This bit looks silly, because it is (see above)
ChangeNick(n) => {
c = Some(ContactManagerCommand::ChangeNick(n));
c = Some(ContactManagerCommand::ChangeNick(n, Recipient::NICKSRC_USER));
},
SetWhatsapp(n) => {
c = Some(ContactManagerCommand::SetWhatsapp(n));
},
PresenceSubscribe => {
self.cf_tx().unbounded_send(ContactFactoryCommand::SubscribePresenceByNick(nick.clone()))
.unwrap();
self.cf_send(ContactFactoryCommand::SubscribePresenceByNick(nick.clone()));
},
Remove => {
self.cf_tx().unbounded_send(ContactFactoryCommand::DropContactByNick(nick.clone()))
.unwrap();
self.cf_send(ContactFactoryCommand::DropContactByNick(nick.clone()));
}
}
if let Some(c) = c {
self.cf_tx().unbounded_send(ContactFactoryCommand::ForwardCommandByNick(nick, c))
.unwrap();
self.cf_send(ContactFactoryCommand::ForwardCommandByNick(nick, c));
}
self.control_response("Ghost command executed.")?;
},
@ -75,8 +72,7 @@ pub trait ControlCommon {
Reinit => ModemCommand::ForceReinit,
TempPath(s) => ModemCommand::UpdatePath(s),
};
self.m_tx().unbounded_send(cts)
.unwrap();
self.m_send(cts);
},
AdminCommand::Whatsapp(wac) => {
use self::AdminWhatsappCommand::*;
@ -88,8 +84,7 @@ pub trait ControlCommon {
UpdateAll => WhatsappCommand::GroupUpdateAll,
PrintAcks => WhatsappCommand::PrintAcks
};
self.wa_tx().unbounded_send(cts)
.unwrap();
self.wa_send(cts);
},
AdminCommand::Group(gc) => {
use self::GroupCommand::*;
@ -98,8 +93,7 @@ pub trait ControlCommon {
BridgeWhatsapp { jid, chan } => WhatsappCommand::GroupAssociate(jid, chan),
Unbridge(ch) => WhatsappCommand::GroupRemove(ch)
};
self.wa_tx().unbounded_send(cts)
.unwrap();
self.wa_send(cts);
},
AdminCommand::Contact(cc) => {
use self::ContactCommand::*;
@ -107,8 +101,7 @@ pub trait ControlCommon {
NewSms(a) => (a, Message::SOURCE_SMS),
NewWhatsapp(a) => (a, Message::SOURCE_WA)
};
self.cf_tx().unbounded_send(ContactFactoryCommand::QueryContact(addr, src))
.unwrap();
self.cf_send(ContactFactoryCommand::QueryContact(addr, src));
},
AdminCommand::Insp(ic) => {
if !self.process_insp(ic)? {

92
src/insp_s2s.rs

@ -13,7 +13,7 @@ use huawei_modem::pdu::{PduAddress, DeliverPdu};
use std::collections::{HashSet, HashMap};
use failure::Error;
use crate::models::Recipient;
use crate::util::{self, Result};
use crate::util::Result;
use crate::contact_common::ContactManagerManager;
use crate::sender_common::Sender;
use crate::control_common::ControlCommon;
@ -97,7 +97,14 @@ impl ContactManagerManager for InspLink {
fn setup_contact_for(&mut self, recip: Recipient, addr: PduAddress) -> Result<()> {
trace!("setting up contact for recip #{}: {}", recip.id, addr);
let host = self.host_for_wa(recip.whatsapp);
let user = InspUser::new_from_recipient(addr.clone(), recip.nick, &host);
let nick = match self.check_nick_for_collisions(&recip.nick) {
Some(n) => {
self.store.update_recipient_nick(&addr, &n, Recipient::NICKSRC_COLLISION)?;
n
},
None => recip.nick
};
let user = InspUser::new_from_recipient(addr.clone(), nick, &host);
let uuid = self.new_user(user)?;
self.contacts.insert(addr.clone(), InspContact {
uuid: uuid.clone(),
@ -125,7 +132,7 @@ impl ContactManagerManager for InspLink {
}
fn store(&mut self) -> &mut Store {
&mut self.store
}
}
fn resolve_nick(&self, nick: &str) -> Option<PduAddress> {
for (uuid, user) in self.users.iter() {
if user.nick == nick {
@ -137,19 +144,36 @@ impl ContactManagerManager for InspLink {
None
}
fn forward_cmd(&mut self, a: &PduAddress, cmd: ContactManagerCommand) -> Result<()> {
if let Some(ct) = self.contacts.get_mut(&a) {
if self.contacts.get(a).is_some() {
match cmd {
ContactManagerCommand::UpdateAway(text) => {
let ct = self.contacts.get(a).unwrap();
self.outbox.push(Message::new(Some(&ct.uuid), "AWAY", vec![], text.as_ref().map(|x| x as &str))?);
},
ContactManagerCommand::ChangeNick(st) => {
ContactManagerCommand::ChangeNick(st, src) => {
{
// Abort if we're trying to change nick to something
// it already is, because otherwise it'll collide.
let ct = self.contacts.get(a).unwrap();
let u = self.users.get(&ct.uuid).unwrap();
if u.nick == st {
warn!("Tried to uselessly change nick for {}!", st);
return Ok(())
}
}
let nick = match self.check_nick_for_collisions(&st) {
Some(n) => n,
None => st.into()
};
let ct = self.contacts.get(a).unwrap();
let u = self.users.get_mut(&ct.uuid).unwrap();
u.nick = st.clone();
u.nick = nick.clone();
let ts = chrono::Utc::now().timestamp().to_string();
self.outbox.push(Message::new(Some(&ct.uuid), "NICK", vec![&st, &ts], None)?);
self.store.update_recipient_nick(&a, &st)?;
self.outbox.push(Message::new(Some(&ct.uuid), "NICK", vec![&nick, &ts], None)?);
self.store.update_recipient_nick(&a, &nick, src)?;
},
ContactManagerCommand::SetWhatsapp(wam) => {
let mut ct = self.contacts.get_mut(a).unwrap();
ct.wa_mode = wam;
self.set_wa_state(&a, wam)?;
},
@ -163,9 +187,18 @@ impl ContactManagerManager for InspLink {
}
}
impl ControlCommon for InspLink {
fn cf_tx(&mut self) -> &mut UnboundedSender<ContactFactoryCommand> { &mut self.cf_tx }
fn wa_tx(&mut self) -> &mut UnboundedSender<WhatsappCommand> { &mut self.wa_tx }
fn m_tx(&mut self) -> &mut UnboundedSender<ModemCommand> { &mut self.m_tx }
fn cf_send(&mut self, c: ContactFactoryCommand) {
self.cf_tx.unbounded_send(c)
.unwrap()
}
fn wa_send(&mut self, c: WhatsappCommand) {
self.wa_tx.unbounded_send(c)
.unwrap()
}
fn m_send(&mut self, c: ModemCommand) {
self.m_tx.unbounded_send(c)
.unwrap()
}
fn control_response(&mut self, msg: &str) -> Result<()> {
if let Some(admu) = self.admin_uuid() {
let line = Message::new(Some(&self.control_uuid), "NOTICE", vec![&admu], Some(msg))?;
@ -308,6 +341,31 @@ impl InspLink {
}
Ok(())
}
fn nick_exists(&self, nick: &str) -> bool {
for (_, user) in self.users.iter() {
if user.nick == nick {
return true;
}
}
false
}
fn check_nick_for_collisions(&self, nick: &str) -> Option<String> {
if self.nick_exists(nick) {
debug!("Nick {} collides with pre-existing nick; finding a new one", nick);
let mut idx = 0;
let mut newnick = format!("{}{}", nick, idx);
while self.nick_exists(&newnick) {
debug!("New nick {} also collides!", newnick);
idx += 1;
newnick = format!("{}{}", nick, idx);
}
info!("Nick {} collides; using non-colliding nick {} instead", nick, newnick);
return Some(newnick);
}
else {
None
}
}
fn remove_user(&mut self, uuid: &str, recreate: bool) -> Result<()> {
debug!("Removing user {} with recreate {}", uuid, recreate);
let addr = if let Some(pdua) = self.contacts_uuid_pdua.get(uuid) {
@ -482,6 +540,7 @@ impl InspLink {
"ENDBURST" => {
if self.remote_sid == prefix {
debug!("Received end of netburst");
self.do_quasiburst()?;
self.state = LinkState::Linked;
self.on_linked()?;
}
@ -549,8 +608,7 @@ impl InspLink {
for grp in self.store.get_all_groups()? {
for part in grp.participants {
if let Some(recip) = self.store.get_recipient_by_id_opt(part)? {
let num = util::un_normalize_address(&recip.phone_number)
.ok_or(format_err!("invalid address {} in db", recip.phone_number))?;
let num = recip.get_addr()?;
if let Some(ct) = self.contacts.get(&num) {
let mode = if grp.admins.contains(&part) {
"+o"
@ -680,8 +738,7 @@ impl InspLink {
};
for msg in self.store.get_all_messages()? {
debug!("Processing message #{}", msg.id);
let addr = util::un_normalize_address(&msg.phone_number)
.ok_or(format_err!("invalid address {} in db", msg.phone_number))?;
let addr = msg.get_addr()?;
if !self.has_contact(&addr) {
if !self.request_contact(addr.clone(), msg.source)? {
continue;
@ -738,10 +795,13 @@ impl InspLink {
self.users.insert(uuid.clone(), cb);
let line = self.make_uid_line(&uuid)?;
self.send(line);
self.send_sid_line("ENDBURST", vec![], None)?;
Ok(())
}
fn do_quasiburst(&mut self) -> Result<()> {
for recip in self.store.get_all_recipients()? {
self.setup_recipient(recip)?;
}
self.send_sid_line("ENDBURST", vec![], None)?;
Ok(())
}
}

10
src/insp_user.rs

@ -15,6 +15,7 @@ pub struct InspUser {
// modes in +abc [args] format
pub modes: String,
pub gecos: String,
pub ours: bool
}
impl InspUser {
pub fn new_from_uid_line(args: Vec<String>, suffix: Option<String>) -> Result<(String, Self)> {
@ -35,7 +36,8 @@ impl InspUser {
ip: args.next().unwrap(),
signon_time: args.next().unwrap().parse()?,
modes: args.collect::<Vec<_>>().join(" "),
gecos: suffix.unwrap()
gecos: suffix.unwrap(),
ours: false
};
Ok((uuid, ret))
}
@ -51,7 +53,8 @@ impl InspUser {
ip: "0.0.0.0".into(),
signon_time: ts,
modes: "+i".into(),
gecos: "sms-irc control bot".into()
gecos: "sms-irc control bot".into(),
ours: true
}
}
pub fn new_from_recipient(a: PduAddress, nick: String, hostname: &str) -> Self {
@ -67,7 +70,8 @@ impl InspUser {
ip: "0.0.0.0".into(),
signon_time: ts,
modes: "+i".into(),
gecos: format!("{}", a)
gecos: format!("{}", a),
ours: true
}
}
}

56
src/irc_s2c.rs

@ -12,15 +12,17 @@ use futures::{Future, Async, Poll, Stream, Sink, self};
use failure::{Error, format_err};
use std::net::{SocketAddr, ToSocketAddrs};
use std::collections::VecDeque;
use std::collections::HashMap;
use huawei_modem::pdu::DeliverPdu;
use crate::util::{Result, self};
use crate::util::Result;
use crate::sender_common::Sender;
use crate::irc_s2c_registration::{PendingIrcConnectionWrapper, RegistrationInformation};
use crate::config::IrcServerConfig;
use crate::comm::InitParameters;
use crate::models::Group;
use crate::comm::*;
use crate::control_common::ControlCommon;
use crate::store::Store;
pub static SERVER_NAME: &str = "sms-irc.";
@ -41,9 +43,11 @@ pub struct IrcConnection {
reginfo: RegistrationInformation,
outbox: Vec<Message>,
store: Store,
joined_groups: Vec<Group>,
/// map from channel name to group info
joined_groups: HashMap<String, Group>,
wa_outbox: VecDeque<WhatsappCommand>,
m_outbox: VecDeque<ModemCommand>,
cf_outbox: VecDeque<ContactFactoryCommand>,
new: bool
}
@ -184,9 +188,10 @@ impl IrcConnection {
Self {
sock, addr, reginfo, store,
outbox: vec![],
joined_groups: vec![],
joined_groups: HashMap::new(),
wa_outbox: VecDeque::new(),
m_outbox: VecDeque::new(),
cf_outbox: VecDeque::new(),
new: true
}
}
@ -201,7 +206,7 @@ impl IrcConnection {
self.reply_s2c("PRIVMSG", vec![], Some(&thing as &str))?;
},
CommandResponse(thing) => {
self.reply_s2c("NOTICE", vec![], Some(&thing as &str))?;
self.outbox.push(Message::new(Some("root"), "PRIVMSG", vec!["&smsirc"], Some(&thing))?);
},
ProcessGroups => {}
}
@ -245,8 +250,7 @@ impl IrcConnection {
for msg in self.store.get_all_messages()? {
debug!("Processing message #{}", msg.id);
let addr = util::un_normalize_address(&msg.phone_number)
.ok_or(format_err!("invalid address {} in db", msg.phone_number))?;
let addr = msg.get_addr()?;
let recip = match self.store.get_recipient_by_addr_opt(&addr)? {
Some(r) => r,
None => {
@ -289,8 +293,7 @@ impl IrcConnection {
self.reply_s2c("353", vec!["@", &grp.channel], Some(&nicks as &str))?;
}
self.reply_s2c("366", vec![&grp.channel], Some("End of /NAMES list."))?;
self.reply_s2c("324", vec![&grp.channel, "+nt"], None)?;
self.joined_groups.push(grp);
self.joined_groups.insert(grp.channel.clone(), grp);
Ok(())
}
fn setup_control_channel(&mut self) -> Result<()> {
@ -328,22 +331,32 @@ impl IrcConnection {
self.reply_from_user("NICK", vec![&new], None)?;
self.reginfo.nick = new;
},
Command::JOIN(chan, _, _) => {
self.reply_s2c("405", vec![&chan], Some("You may not manually /JOIN channels in this alpha version."))?;
Command::JOIN(_, _, _) => {
// Just ignore /JOIN requests at present, since we autojoin.
},
Command::PART(chan, _) => {
// This is ERR_NOTONCHANNEL, which isn't amazing.
self.reply_s2c("442", vec![&chan], Some("You may not manually /PART channels in this alpha version."))?;
self.reply_s2c("442", vec![&chan], Some("You may not part."))?;
},
Command::ChannelMODE(target, modes) => {
if modes.len() > 0 {
self.reply_s2c("482", vec![&target], Some("You may not alter channel modes."))?;
}
else {
self.reply_s2c("324", vec![&target, "+nt"], None)?;
}
},
Command::PRIVMSG(target, msg) => {
if target.starts_with("#") {
if target == "&smsirc" {
self.process_admin_command(msg)?;
}
else if target.starts_with("#") {
// FIXME: check the channel actually exists
self.wa_outbox.push_back(WhatsappCommand::SendGroupMessage(target, msg));
}
else {
if let Some(recip) = self.store.get_recipient_by_nick_opt(&target)? {
let addr = util::un_normalize_address(&recip.phone_number)
.ok_or(format_err!("unnormalizable addr"))?;
let addr = recip.get_addr()?;
if recip.whatsapp {
self.wa_outbox.push_back(WhatsappCommand::SendDirectMessage(addr, msg));
}
@ -368,6 +381,21 @@ impl IrcConnection {
}
}
impl ControlCommon for IrcConnection {
fn cf_send(&mut self, c: ContactFactoryCommand) {
self.cf_outbox.push_back(c);
}
fn wa_send(&mut self, c: WhatsappCommand) {
self.wa_outbox.push_back(c);
}
fn m_send(&mut self, c: ModemCommand) {
self.m_outbox.push_back(c);
}
fn control_response(&mut self, msg: &str) -> Result<()> {
self.outbox.push(Message::new(Some("root"), "PRIVMSG", vec!["&smsirc"], Some(msg))?);
Ok(())
}
}
impl Sender for IrcConnection {
fn report_error(&mut self, from_nick: &str, err: String) -> Result<()> {
self.reply_from_nick(from_nick, "NOTICE", vec![&self.reginfo.nick.clone()], Some(&err as &str))?;

31
src/models.rs

@ -1,6 +1,8 @@
use crate::schema::{recipients, messages, groups, wa_persistence, wa_msgids};
use serde_json::Value;
use chrono::NaiveDateTime;
use huawei_modem::pdu::PduAddress;
use crate::util::{self, Result};
#[derive(Queryable)]
pub struct Recipient {
@ -10,6 +12,26 @@ pub struct Recipient {
pub whatsapp: bool,
pub avatar_url: Option<String>,
pub notify: Option<String>,
pub nicksrc: i32,
}
impl Recipient {
/// Nick source: migrated from previous sms-irc install
pub const NICKSRC_MIGRATED: i32 = -1;
/// Nick source: autogenerated from phone number
pub const NICKSRC_AUTO: i32 = 0;
/// Nick source: renamed by user
pub const NICKSRC_USER: i32 = 1;
/// Nick source: from WhatsApp contact name
pub const NICKSRC_WA_CONTACT: i32 = 2;
/// Nick source: from WhatsApp notify
pub const NICKSRC_WA_NOTIFY: i32 = 3;
/// Nick source: from a nick collision
pub const NICKSRC_COLLISION: i32 = 4;
pub fn get_addr(&self) -> Result<PduAddress> {
let addr = util::un_normalize_address(&self.phone_number)
.ok_or(format_err!("invalid address {} in db", self.phone_number))?;
Ok(addr)
}
}
#[derive(Insertable)]
#[table_name="recipients"]
@ -18,7 +40,8 @@ pub struct NewRecipient<'a> {
pub nick: &'a str,
pub whatsapp: bool,
pub avatar_url: Option<&'a str>,
pub notify: Option<&'a str>
pub notify: Option<&'a str>,
pub nicksrc: i32
}
#[derive(Queryable, Debug)]
pub struct Message {
@ -34,6 +57,12 @@ pub struct Message {
impl Message {
pub const SOURCE_SMS: i32 = 0;
pub const SOURCE_WA: i32 = 1;
pub fn get_addr(&self) -> Result<PduAddress> {
let addr = util::un_normalize_address(&self.phone_number)
.ok_or(format_err!("invalid address {} in db", self.phone_number))?;
Ok(addr)
}
}
#[derive(Queryable, Debug)]
pub struct Group {

1
src/schema.rs

@ -30,6 +30,7 @@ table! {
whatsapp -> Bool,
avatar_url -> Nullable<Varchar>,
notify -> Nullable<Varchar>,
nicksrc -> Int4,
}
}

48
src/store.rs

@ -12,6 +12,7 @@ use whatsappweb::session::PersistentSession;
use whatsappweb::Jid;
use crate::util::{self, Result};
use chrono::NaiveDateTime;
use regex::Regex;
use crate::models::*;
embed_migrations!();
@ -26,9 +27,31 @@ impl Store {
let pool = Pool::builder()
.build(manager)?;
embedded_migrations::run(&*pool.get()?)?;
Ok(Self {
let mut ret = Self {
inner: Arc::new(pool)
})
};
let sourceless = ret.get_recipients_with_nicksrc(Recipient::NICKSRC_MIGRATED)?;
if sourceless.len() > 0 {
warn!("Adding nickname sources for migrated recipients");
// Use heuristics to match nicknames which look like they've been
// automatically generated
lazy_static! {
static ref DEFAULT_RE: Regex = Regex::new(r#"I\d+"#).unwrap();
}
for r in sourceless {
let addr = r.get_addr()?;
let newsrc = if DEFAULT_RE.is_match(&r.nick) {
Recipient::NICKSRC_AUTO
}
else {
// We can't assume much, so set to NICKSRC_USER so
// nothing overwrites it
Recipient::NICKSRC_USER
};
ret.update_recipient_nick(&addr, &r.nick, newsrc)?;
}
}
Ok(ret)
}
pub fn store_sms_message(&mut self, addr: &PduAddress, pdu: &[u8], csms_data: Option<i32>) -> Result<Message> {
use crate::schema::messages;
@ -153,7 +176,8 @@ impl Store {
nick,
whatsapp: false,
avatar_url: None,
notify: None
notify: None,
nicksrc: Recipient::NICKSRC_AUTO
};
let conn = self.inner.get()?;
@ -162,7 +186,7 @@ impl Store {
.get_result(&*conn)?;
Ok(res)
}
pub fn store_wa_recipient(&mut self, addr: &PduAddress, nick: &str, notify: Option<&str>) -> Result<Recipient> {
pub fn store_wa_recipient(&mut self, addr: &PduAddress, nick: &str, notify: Option<&str>, nicksrc: i32) -> Result<Recipient> {
use crate::schema::recipients;
let num = util::normalize_address(addr);
@ -171,7 +195,8 @@ impl Store {
nick,
whatsapp: true,
avatar_url: None,
notify: notify
notify: notify,
nicksrc
};
let conn = self.inner.get()?;
@ -191,14 +216,14 @@ impl Store {
.execute(&*conn)?;
Ok(())
}
pub fn update_recipient_nick(&mut self, addr: &PduAddress, n: &str) -> Result<()> {
pub fn update_recipient_nick(&mut self, addr: &PduAddress, n: &str, src: i32) -> Result<()> {
use crate::schema::recipients::dsl::*;
let conn = self.inner.get()?;
let num = util::normalize_address(addr);
::diesel::update(recipients)
.filter(phone_number.eq(num))
.set(nick.eq(n))
.set((nick.eq(n), nicksrc.eq(src)))
.execute(&*conn)?;
Ok(())
}
@ -249,6 +274,15 @@ impl Store {
.load(&*conn)?;
Ok(res)
}
pub fn get_recipients_with_nicksrc(&mut self, ns: i32) -> Result<Vec<Recipient>> {
use crate::schema::recipients::dsl::*;
let conn = self.inner.get()?;
let res = recipients
.filter(nicksrc.eq(ns))
.load(&*conn)?;
Ok(res)
}
pub fn get_all_messages(&mut self) -> Result<Vec<Message>> {
use crate::schema::messages::dsl::*;
let conn = self.inner.get()?;

66
src/whatsapp.rs

@ -449,17 +449,24 @@ impl WhatsappManager {
}
Ok(())
}
fn get_contact_notify_for_jid(&mut self, jid: &Jid) -> Option<&str> {
let mut notify: Option<&str> = None;
fn get_nick_for_jid(&mut self, jid: &Jid) -> Result<(String, i32)> {
if let Some(ct) = self.contacts.get(jid) {
if let Some(ref name) = ct.name {
notify = Some(name);
let nick = util::string_to_irc_nick(&name);
return Ok((nick, Recipient::NICKSRC_WA_CONTACT));
}
else if let Some(ref name) = ct.notify {
notify = Some(name);
let nick = util::string_to_irc_nick(&name);
return Ok((nick, Recipient::NICKSRC_WA_NOTIFY));
}
}
notify
let addr = match util::jid_to_address(jid) {
Some(a) => a,
None => {
return Err(format_err!("couldn't translate jid {} to address", jid));
}
};
Ok((util::make_nick_for_address(&addr), Recipient::NICKSRC_AUTO))
}
fn get_wa_recipient(&mut self, jid: &Jid) -> Result<Recipient> {
let addr = match util::jid_to_address(jid) {
@ -472,13 +479,10 @@ impl WhatsappManager {
Ok(recip)
}
else {
let notify = self.get_contact_notify_for_jid(jid).map(|x| x.to_string());
let nick = match notify {
Some(ref n) => util::string_to_irc_nick(n),
None => util::make_nick_for_address(&addr)
};
info!("Creating new WA recipient for {} (nick {})", addr, nick);
let ret = self.store.store_wa_recipient(&addr, &nick, notify.as_ref().map(|x| x as &str))?;
let (nick, nicksrc) = self.get_nick_for_jid(jid)?;
info!("Creating new WA recipient for {} (nick {}, src {})", addr, nick, nicksrc);
let notify = self.contacts.get(jid).and_then(|x| x.notify.as_ref().map(|x| x as &str));
let ret = self.store.store_wa_recipient(&addr, &nick, notify, nicksrc)?;
self.cf_tx.unbounded_send(ContactFactoryCommand::SetupContact(addr.clone()))
.unwrap();
Ok(ret)
@ -650,25 +654,29 @@ impl WhatsappManager {
fn on_contact_change(&mut self, ct: WaContact) -> Result<()> {
let jid = ct.jid.clone();
if let Some(addr) = util::jid_to_address(&jid) {
let old_notify = self.get_contact_notify_for_jid(&jid).map(|x| x.to_string());
self.contacts.insert(ct.jid.clone(), ct);
let recip = self.get_wa_recipient(&jid)?;
let notify = self.get_contact_notify_for_jid(&jid).map(|x| x.to_string());
if old_notify != notify {
debug!("Notify changed for recipient {}: it's now {:?}", recip.nick, notify);
self.store.update_recipient_notify(&addr, notify.as_ref().map(|x| x as &str))?;
if self.autoupdate_nicks && old_notify.is_none() {
if let Some(n) = notify {
let nick = util::string_to_irc_nick(&n);
info!("Automatically updating nick for {} to {}", addr, nick);
self.store.update_recipient_nick(&addr, &nick)?;
let cmd = ContactFactoryCommand::ForwardCommand(
addr,
crate::comm::ContactManagerCommand::ChangeNick(nick)
);
self.cf_tx.unbounded_send(cmd)
.unwrap();
}
let (new_nick, newsrc) = self.get_nick_for_jid(&jid)?;
let notify = self.contacts.get(&jid).and_then(|x| x.notify.as_ref().map(|x| x as &str));
if notify.is_some() {
self.store.update_recipient_notify(&addr, notify)?;
}
if recip.nick != new_nick {
debug!("New nick '{}' (src {}) for recipient {} (from '{}', src {})", new_nick, newsrc, addr, recip.nick, recip.nicksrc);
let should_update = match (recip.nicksrc, newsrc) {
(Recipient::NICKSRC_MIGRATED, _) => true,
(Recipient::NICKSRC_AUTO, _) => true,
(Recipient::NICKSRC_WA_NOTIFY, Recipient::NICKSRC_WA_CONTACT) => true,
_ => false
};
if should_update && self.autoupdate_nicks {
info!("Automatically updating nick for {} to {}", addr, new_nick);
let cmd = ContactFactoryCommand::ForwardCommand(
addr,
crate::comm::ContactManagerCommand::ChangeNick(new_nick, newsrc)
);
self.cf_tx.unbounded_send(cmd)
.unwrap();
}
}
}

Loading…
Cancel
Save