Skip to content

Commit

Permalink
Depend on presage commit ff708a1.
Browse files Browse the repository at this point in the history
  • Loading branch information
hoehermann committed Nov 4, 2024
1 parent ad45f3f commit b901aab
Show file tree
Hide file tree
Showing 5 changed files with 148 additions and 115 deletions.
6 changes: 3 additions & 3 deletions src/rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ name = "purple_presage_backend"
path = "src/lib.rs"
crate-type = ["staticlib"]

# presage needs this, taken from https://github.com/whisperfish/presage/blob/b0e862b/Cargo.toml
# presage needs this, taken from https://github.com/whisperfish/presage/blob/ff708a1/Cargo.toml
[patch.crates-io]
curve25519-dalek = { git = 'https://github.com/signalapp/curve25519-dalek', tag = 'signal-curve25519-4.1.3' }

[dependencies]
presage = { git = "https://github.com/whisperfish/presage", rev = "b0e862b" }
presage-store-sled = { git = "https://github.com/whisperfish/presage", rev = "b0e862b" }
presage = { git = "https://github.com/whisperfish/presage", rev = "ff708a1" }
presage-store-sled = { git = "https://github.com/whisperfish/presage", rev = "ff708a1" }
mime-sniffer = { git = "https://github.com/kamadorueda/rust-mime-sniffer"}
hex = "*"
chrono = "*"
Expand Down
18 changes: 10 additions & 8 deletions src/rust/src/contacts.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
pub fn get_contacts<C: presage::store::Store + 'static>(
pub async fn get_contacts<C: presage::store::Store + 'static>(
account: *const std::os::raw::c_void,
manager: Option<presage::Manager<C, presage::manager::Registered>>,
) -> Result<presage::Manager<C, presage::manager::Registered>, presage::Error<<C>::Error>> {
let manager = manager.expect("manager must be loaded");
let mut message = crate::bridge::Presage::from_account(account);
let groups: Vec<crate::bridge::Group> = manager
.store()
.contacts()?
.contacts()
.await?
.flatten()
.map(
|presage::libsignal_service::models::Contact {
|presage::model::contacts::Contact {
name,
uuid,
phone_number,
Expand Down Expand Up @@ -38,13 +39,13 @@ pub fn get_contacts<C: presage::store::Store + 'static>(
Ok(manager)
}

pub fn get_group_members<C: presage::store::Store + 'static>(
pub async fn get_group_members<C: presage::store::Store + 'static>(
account: *const std::os::raw::c_void,
manager: Option<presage::Manager<C, presage::manager::Registered>>,
key: [u8; 32],
) -> Result<presage::Manager<C, presage::manager::Registered>, presage::Error<<C>::Error>> {
let manager = manager.expect("manager must be loaded");
match manager.store().group(key)? {
match manager.store().group(key).await? {
Some(group) => {
let mut message = crate::bridge::Presage::from_account(account);
let uuid_strings = group.members.into_iter().map(|member| member.uuid.to_string());
Expand All @@ -69,20 +70,21 @@ pub fn get_group_members<C: presage::store::Store + 'static>(
Ok(manager)
}

pub fn get_groups<C: presage::store::Store + 'static>(
pub async fn get_groups<C: presage::store::Store + 'static>(
account: *const std::os::raw::c_void,
manager: Option<presage::Manager<C, presage::manager::Registered>>,
) -> Result<presage::Manager<C, presage::manager::Registered>, presage::Error<<C>::Error>> {
let manager = manager.expect("manager must be loaded");
let mut message = crate::bridge::Presage::from_account(account);
let groups: Vec<crate::bridge::Group> = manager
.store()
.groups()?
.groups()
.await?
.flatten()
.map(
|(
group_master_key,
presage::libsignal_service::groups_v2::Group {
presage::model::groups::Group {
title,
description,
revision,
Expand Down
12 changes: 6 additions & 6 deletions src/rust/src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,8 @@ async fn run<C: presage::store::Store + 'static>(
crate::core::purple_debug(account, 2, format!("InitialSync completed.\n"));

// also, fetch contacts and groups now
manager = crate::contacts::get_contacts(account, Some(manager))?;
manager = crate::contacts::get_groups(account, Some(manager))?;
manager = crate::contacts::get_contacts(account, Some(manager)).await?;
manager = crate::contacts::get_groups(account, Some(manager)).await?;

// now that the initial sync has completed,
// the connection can be regarded as "connected" and ready to send messages
Expand Down Expand Up @@ -188,9 +188,9 @@ async fn run<C: presage::store::Store + 'static>(
Ok(manager)
}

crate::structs::Cmd::ListGroups => crate::contacts::get_groups(account, manager),
crate::structs::Cmd::ListGroups => crate::contacts::get_groups(account, manager).await,

crate::structs::Cmd::GetGroupMembers { master_key_bytes } => crate::contacts::get_group_members(account, manager, master_key_bytes),
crate::structs::Cmd::GetGroupMembers { master_key_bytes } => crate::contacts::get_group_members(account, manager, master_key_bytes).await,

crate::structs::Cmd::Exit {} => {
purple_error(account, 16, String::from("Exit command reached inner loop."));
Expand Down Expand Up @@ -273,8 +273,8 @@ pub async fn main(
) {
purple_debug(account, 2, format!("opening config database from {store_path}\n"));
let config_store =
presage_store_sled::SledStore::open_with_passphrase(store_path, passphrase, presage_store_sled::MigrationConflictStrategy::Raise, presage_store_sled::OnNewIdentity::Trust);
match config_store {
presage_store_sled::SledStore::open_with_passphrase(store_path, passphrase, presage_store_sled::MigrationConflictStrategy::Raise, presage::model::identity::OnNewIdentity::Trust);
match config_store.await {
Err(err) => {
purple_error(account, 16, format!("config_store Err {err:?}"));
}
Expand Down
225 changes: 128 additions & 97 deletions src/rust/src/receive.rs
Original file line number Diff line number Diff line change
@@ -1,104 +1,135 @@
use futures::StreamExt; // for Stream.next()

/*
* Prepares a received message's text for the front-end.
*
* Based on presage-cli's `print_message`.
*/
fn print_message<C: presage::store::Store>(
manager: &presage::Manager<C, presage::manager::Registered>,
content: &presage::libsignal_service::content::Content,
account: *const std::os::raw::c_void,
) {
crate::core::purple_debug(account, 2, String::from("print_message called…\n"));
let Ok(thread) = presage::store::Thread::try_from(content) else {
crate::core::purple_error(account, 16, String::from("failed to derive thread from content"));
return;
};
/**
Looks up the title of a group identified by its group master key.
Adapted from presage-cli.
*/
async fn format_group<S: presage::store::Store>(key: [u8; 32], manager: &presage::Manager<S, presage::manager::Registered>) -> String {
manager
.store()
.group(key)
.await
.ok()
.flatten()
.map(|g| g.title)
.unwrap_or_else(|| "<missing group>".to_string())
}

/**
Looks up the display name for a contact identified by their uuid.
Adapted from presage-cli.
*/
async fn format_contact<S: presage::store::Store>(uuid: &presage::libsignal_service::prelude::Uuid, manager: &presage::Manager<S, presage::manager::Registered>) -> String {
manager
.store()
.contact_by_id(uuid)
.await
.ok()
.flatten()
.filter(|c| !c.name.is_empty())
.map(|c| c.name)
.unwrap_or_else(|| uuid.to_string())
}

let format_data_message = |thread: &presage::store::Thread, data_message: &presage::libsignal_service::content::DataMessage| {
match data_message {
// Quote
presage::libsignal_service::content::DataMessage {
quote: Some(presage::proto::data_message::Quote {
text: Some(quoted_text),
/**
Turns a DataMessage into a string for presentation via libpurple.
Adapted from presage-cli.
*/
async fn format_data_message<S: presage::store::Store>(
thread: &presage::store::Thread,
data_message: &presage::libsignal_service::content::DataMessage,
manager: &presage::Manager<S, presage::manager::Registered>,
account: *const std::os::raw::c_void,
) -> Option<String> {
match data_message {
// Quote
presage::libsignal_service::content::DataMessage {
quote: Some(presage::proto::data_message::Quote {
text: Some(quoted_text),
..
}),
body: Some(body),
..
} => {
let firstline = quoted_text.split("\n").next().unwrap_or("<message body missing>");
// TODO: add ellipsis if quoted_text contains more than one line
Some(format!("> {firstline}\n\n{body}"))
}
// Reaction
presage::libsignal_service::content::DataMessage {
reaction:
Some(presage::proto::data_message::Reaction {
target_sent_timestamp: Some(timestamp),
emoji: Some(emoji),
..
}),
body: Some(body),
..
} => {
let firstline = quoted_text.split("\n").next().unwrap_or("<message body missing>");
// TODO: add ellipsis if quoted_text contains more than one line
Some(format!("> {firstline}\n\n{body}"))
}
// Reaction
presage::libsignal_service::content::DataMessage {
reaction:
Some(presage::proto::data_message::Reaction {
target_sent_timestamp: Some(timestamp),
emoji: Some(emoji),
..
} => {
let Ok(Some(message)) = manager.store().message(thread, *timestamp).await else {
// Original message could not be found. As a best effort, give some reference by displaying the timestamp.
let sent_at =
chrono::prelude::DateTime::<chrono::Local>::from(std::time::UNIX_EPOCH + std::time::Duration::from_millis(*timestamp)).format("%Y-%m-%d %H:%M:%S");
return Some(format!("Reacted with {emoji} to message from {sent_at}."));
};

let (presage::libsignal_service::content::ContentBody::DataMessage(presage::libsignal_service::content::DataMessage {
body: Some(body), ..
})
| presage::libsignal_service::content::ContentBody::SynchronizeMessage(presage::libsignal_service::content::SyncMessage {
sent:
Some(presage::proto::sync_message::Sent {
message: Some(presage::libsignal_service::content::DataMessage {
body: Some(body), ..
}),
..
}),
..
} => {
let Ok(Some(message)) = manager.store().message(thread, *timestamp) else {
// Original message could not be found. As a best effort, give some reference by displaying the timestamp.
let sent_at =
chrono::prelude::DateTime::<chrono::Local>::from(std::time::UNIX_EPOCH + std::time::Duration::from_millis(*timestamp)).format("%Y-%m-%d %H:%M:%S");
return Some(format!("Reacted with {emoji} to message from {sent_at}."));
};

let (presage::libsignal_service::content::ContentBody::DataMessage(presage::libsignal_service::content::DataMessage {
body: Some(body), ..
})
| presage::libsignal_service::content::ContentBody::SynchronizeMessage(presage::libsignal_service::content::SyncMessage {
sent:
Some(presage::proto::sync_message::Sent {
message: Some(presage::libsignal_service::content::DataMessage {
body: Some(body), ..
}),
..
}),
..
})) = message.body
else {
// Sometimes, synced messages are not resolved here and reactions to them end up in this arm.
let sent_at =
chrono::prelude::DateTime::<chrono::Local>::from(std::time::UNIX_EPOCH + std::time::Duration::from_millis(*timestamp)).format("%Y-%m-%d %H:%M:%S");
return Some(format!("Reacted with {emoji} to message from {sent_at}."));
};
let firstline = body.split("\n").next().unwrap_or("<message body missing>");
// TODO: add ellipsis if body contains more than one line
Some(format!("Reacted with {emoji} to message „{firstline}“."))
}
// Plain text message
// TODO: resolve mentions
presage::libsignal_service::content::DataMessage {
body: Some(body), ..
} => Some(body.to_string()),
// Default (catch all other cases)
c => {
crate::core::purple_debug(account, 2, format!("DataMessage without body {c:?}\n"));
// NOTE: This happens when receiving a file, but not providing a text
// TODO: suppress this debug message if data message contained an attachment
// NOTE: flags: Some(4) with a timestamp (and a profile_key?) may indicate "message sent"
// Some("message has been sent".to_string())
None
}
})) = message.body
else {
// Sometimes, synced messages are not resolved here and reactions to them end up in this arm.
let sent_at =
chrono::prelude::DateTime::<chrono::Local>::from(std::time::UNIX_EPOCH + std::time::Duration::from_millis(*timestamp)).format("%Y-%m-%d %H:%M:%S");
return Some(format!("Reacted with {emoji} to message from {sent_at}."));
};
let firstline = body.split("\n").next().unwrap_or("<message body missing>");
// TODO: add ellipsis if body contains more than one line
Some(format!("Reacted with {emoji} to message „{firstline}“."))
}
};
// Plain text message
// TODO: resolve mentions
presage::libsignal_service::content::DataMessage {
body: Some(body), ..
} => Some(body.to_string()),
// Default (catch all other cases)
c => {
crate::core::purple_debug(account, 2, format!("DataMessage without body {c:?}\n"));
// NOTE: This happens when receiving a file, but not providing a text
// TODO: suppress this debug message if data message contained an attachment
// NOTE: flags: Some(4) with a timestamp (and a profile_key?) may indicate "message sent"
// Some("message has been sent".to_string())
None
}
}
}

let format_contact = |uuid| {
manager
.store()
.contact_by_id(uuid)
.ok()
.flatten()
.filter(|c| !c.name.is_empty())
.map(|c| c.name)
.unwrap_or_else(|| uuid.to_string())
/**
Prepares a received message's text for the front-end.
Adapted from presage-cli.
*/
async fn print_message<C: presage::store::Store>(
manager: &presage::Manager<C, presage::manager::Registered>,
content: &presage::libsignal_service::content::Content,
account: *const std::os::raw::c_void,
) {
crate::core::purple_debug(account, 2, String::from("print_message called…\n"));
let Ok(thread) = presage::store::Thread::try_from(content) else {
crate::core::purple_error(account, 16, String::from("failed to derive thread from content"));
return;
};
let group_get_title = |key| manager.store().group(key).ok().flatten().map(|g| g.title).unwrap_or_else(|| "<missing group>".to_string());

enum Msg<'a> {
Received(&'a presage::store::Thread, String),
Expand All @@ -107,14 +138,14 @@ fn print_message<C: presage::store::Store>(

if let Some(msg) = match &content.body {
presage::libsignal_service::content::ContentBody::NullMessage(_) => Some(Msg::Received(&thread, "Null message (for example deleted)".to_string())),
presage::libsignal_service::content::ContentBody::DataMessage(data_message) => format_data_message(&thread, data_message).map(|body| Msg::Received(&thread, body)),
presage::libsignal_service::content::ContentBody::DataMessage(data_message) => format_data_message(&thread, data_message, manager, account).await.map(|body| Msg::Received(&thread, body)),
presage::libsignal_service::content::ContentBody::SynchronizeMessage(presage::libsignal_service::content::SyncMessage {
sent: Some(presage::proto::sync_message::Sent {
message: Some(data_message),
..
}),
..
}) => format_data_message(&thread, data_message).map(|body| Msg::Sent(&thread, body)),
}) => format_data_message(&thread, data_message, manager, account).await.map(|body| Msg::Sent(&thread, body)),
presage::libsignal_service::content::ContentBody::CallMessage(_) => Some(Msg::Received(&thread, "is calling!".into())),
// TODO: forward these properly
presage::libsignal_service::content::ContentBody::TypingMessage(_) => None, //Some(Msg::Received(&thread, "is typing...".into())), // too annyoing for now. also does not differentiate between "started typing" and "stopped typing"
Expand All @@ -132,7 +163,7 @@ fn print_message<C: presage::store::Store>(
Msg::Received(presage::store::Thread::Contact(sender), body) => {
message.flags = 0x0002; // PURPLE_MESSAGE_RECV
message.who = std::ffi::CString::new(sender.to_string()).unwrap().into_raw();
message.name = std::ffi::CString::new(format_contact(sender)).unwrap().into_raw();
message.name = std::ffi::CString::new(format_contact(sender, manager).await).unwrap().into_raw();
message.body = std::ffi::CString::new(body).unwrap().into_raw();
}
Msg::Sent(presage::store::Thread::Contact(recipient), body) => {
Expand All @@ -143,15 +174,15 @@ fn print_message<C: presage::store::Store>(
Msg::Received(presage::store::Thread::Group(key), body) => {
message.flags = 0x0002; // PURPLE_MESSAGE_RECV
message.who = std::ffi::CString::new(content.metadata.sender.uuid.to_string()).unwrap().into_raw();
message.name = std::ffi::CString::new(format_contact(&content.metadata.sender.uuid)).unwrap().into_raw();
message.name = std::ffi::CString::new(format_contact(&content.metadata.sender.uuid, manager).await).unwrap().into_raw();
message.group = std::ffi::CString::new(hex::encode(key)).unwrap().into_raw();
message.title = std::ffi::CString::new(group_get_title(*key)).unwrap().into_raw();
message.title = std::ffi::CString::new(format_group(*key, manager).await).unwrap().into_raw();
message.body = std::ffi::CString::new(body).unwrap().into_raw();
}
Msg::Sent(presage::store::Thread::Group(key), body) => {
message.flags = 0x0001 | 0x10000; // PURPLE_MESSAGE_SEND | PURPLE_MESSAGE_REMOTE_SEND
message.group = std::ffi::CString::new(hex::encode(key)).unwrap().into_raw();
message.title = std::ffi::CString::new(group_get_title(*key)).unwrap().into_raw();
message.title = std::ffi::CString::new(format_group(*key, manager).await).unwrap().into_raw();
message.body = std::ffi::CString::new(body).unwrap().into_raw();
}
};
Expand All @@ -169,7 +200,7 @@ async fn process_incoming_message<C: presage::store::Store>(
content: &presage::libsignal_service::content::Content,
account: *const std::os::raw::c_void,
) {
print_message(manager, content, account);
print_message(manager, content, account).await;

if let presage::libsignal_service::content::ContentBody::DataMessage(presage::libsignal_service::content::DataMessage { attachments, .. })
| presage::libsignal_service::content::ContentBody::SynchronizeMessage(presage::libsignal_service::content::SyncMessage {
Expand Down
Loading

0 comments on commit b901aab

Please sign in to comment.