Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
37d3380c55 | ||
|
|
9fcfb9f0bf |
@@ -21,6 +21,7 @@ mod room;
|
|||||||
|
|
||||||
pub use message::*;
|
pub use message::*;
|
||||||
pub use room::*;
|
pub use room::*;
|
||||||
|
pub use state::FileAttachment;
|
||||||
|
|
||||||
/// A static keypair used only for signing locally-cached rumor events.
|
/// A static keypair used only for signing locally-cached rumor events.
|
||||||
static LOCAL_KEYS: LazyLock<Keys> = LazyLock::new(Keys::generate);
|
static LOCAL_KEYS: LazyLock<Keys> = LazyLock::new(Keys::generate);
|
||||||
|
|||||||
+79
-36
@@ -4,6 +4,9 @@ use std::ops::Range;
|
|||||||
use common::{EventExt, NostrParser, extract_and_remove_media_urls};
|
use common::{EventExt, NostrParser, extract_and_remove_media_urls};
|
||||||
use gpui::{SharedString, SharedUri};
|
use gpui::{SharedString, SharedUri};
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
|
use state::FileAttachment;
|
||||||
|
|
||||||
|
pub const KIND_FILE_MESSAGE: Kind = Kind::Custom(15);
|
||||||
|
|
||||||
/// Rendered message.
|
/// Rendered message.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -21,61 +24,90 @@ pub struct Message {
|
|||||||
pub mentions: Vec<Mention>,
|
pub mentions: Vec<Mention>,
|
||||||
/// List of event of the message this message is a reply to
|
/// List of event of the message this message is a reply to
|
||||||
pub replies_to: Vec<EventId>,
|
pub replies_to: Vec<EventId>,
|
||||||
|
/// Encrypted file attachment
|
||||||
|
pub file: Option<FileAttachment>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&Event> for Message {
|
impl From<&Event> for Message {
|
||||||
fn from(val: &Event) -> Self {
|
fn from(val: &Event) -> Self {
|
||||||
let mentions = extract_mentions(&val.content);
|
from_parts(
|
||||||
let replies_to = extract_reply_ids(&val.tags);
|
val.id,
|
||||||
let (media, string) = extract_and_remove_media_urls(&val.content);
|
val.pubkey,
|
||||||
|
val.created_at,
|
||||||
Self {
|
val.kind,
|
||||||
id: val.id,
|
&val.content,
|
||||||
author: val.pubkey,
|
&val.tags,
|
||||||
content: string,
|
)
|
||||||
media,
|
|
||||||
created_at: val.created_at,
|
|
||||||
mentions,
|
|
||||||
replies_to,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&UnsignedEvent> for Message {
|
impl From<&UnsignedEvent> for Message {
|
||||||
fn from(val: &UnsignedEvent) -> Self {
|
fn from(val: &UnsignedEvent) -> Self {
|
||||||
let mentions = extract_mentions(&val.content);
|
from_parts(
|
||||||
let replies_to = extract_reply_ids(&val.tags);
|
|
||||||
let (media, string) = extract_and_remove_media_urls(&val.content);
|
|
||||||
|
|
||||||
Self {
|
|
||||||
// Event ID must be known
|
// Event ID must be known
|
||||||
id: val.id.unwrap(),
|
val.id.unwrap(),
|
||||||
author: val.pubkey,
|
val.pubkey,
|
||||||
content: string,
|
val.created_at,
|
||||||
media,
|
val.kind,
|
||||||
created_at: val.created_at,
|
&val.content,
|
||||||
mentions,
|
&val.tags,
|
||||||
replies_to,
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&NewMessage> for Message {
|
impl From<&NewMessage> for Message {
|
||||||
fn from(val: &NewMessage) -> Self {
|
fn from(val: &NewMessage) -> Self {
|
||||||
let mentions = extract_mentions(&val.rumor.content);
|
from_parts(
|
||||||
let replies_to = extract_reply_ids(&val.rumor.tags);
|
|
||||||
let (media, string) = extract_and_remove_media_urls(&val.rumor.content);
|
|
||||||
|
|
||||||
Self {
|
|
||||||
// Event ID must be known
|
// Event ID must be known
|
||||||
id: val.rumor.id.unwrap(),
|
val.rumor.id.unwrap(),
|
||||||
author: val.rumor.pubkey,
|
val.rumor.pubkey,
|
||||||
content: string,
|
val.rumor.created_at,
|
||||||
|
val.rumor.kind,
|
||||||
|
&val.rumor.content,
|
||||||
|
&val.rumor.tags,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_parts(
|
||||||
|
id: EventId,
|
||||||
|
author: PublicKey,
|
||||||
|
created_at: Timestamp,
|
||||||
|
kind: Kind,
|
||||||
|
content: &str,
|
||||||
|
tags: &Tags,
|
||||||
|
) -> Message {
|
||||||
|
let file = if kind == KIND_FILE_MESSAGE {
|
||||||
|
FileAttachment::from_tags(content, tags)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let has_file = file.is_some();
|
||||||
|
|
||||||
|
let replies_to = extract_reply_ids(tags);
|
||||||
|
|
||||||
|
// For file messages `.content` is the encrypted blob URL, not text or media
|
||||||
|
let mentions = if has_file {
|
||||||
|
Vec::new()
|
||||||
|
} else {
|
||||||
|
extract_mentions(content)
|
||||||
|
};
|
||||||
|
|
||||||
|
let (media, content) = if has_file {
|
||||||
|
(Vec::new(), String::new())
|
||||||
|
} else {
|
||||||
|
extract_and_remove_media_urls(content)
|
||||||
|
};
|
||||||
|
|
||||||
|
Message {
|
||||||
|
id,
|
||||||
|
author,
|
||||||
|
content,
|
||||||
media,
|
media,
|
||||||
created_at: val.rumor.created_at,
|
created_at,
|
||||||
mentions,
|
mentions,
|
||||||
replies_to,
|
replies_to,
|
||||||
}
|
file,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,6 +137,17 @@ impl Hash for Message {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Message {
|
||||||
|
/// Single-line representation for reply previews, notifications and copy.
|
||||||
|
pub fn preview(&self) -> SharedString {
|
||||||
|
if let Some(file) = &self.file {
|
||||||
|
return format!("[File] {}", file.display_name()).into();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.content.clone().into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// New message.
|
/// New message.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub struct NewMessage {
|
pub struct NewMessage {
|
||||||
|
|||||||
+46
-15
@@ -12,7 +12,7 @@ use person::{Person, PersonRegistry};
|
|||||||
use settings::{RoomConfig, SignerKind};
|
use settings::{RoomConfig, SignerKind};
|
||||||
use state::{NostrRegistry, TIMEOUT, UniversalSigner};
|
use state::{NostrRegistry, TIMEOUT, UniversalSigner};
|
||||||
|
|
||||||
use crate::NewMessage;
|
use crate::{FileAttachment, KIND_FILE_MESSAGE, NewMessage};
|
||||||
|
|
||||||
const NO_DEKEY: &str = "User hasn't set up a decoupled encryption key yet.";
|
const NO_DEKEY: &str = "User hasn't set up a decoupled encryption key yet.";
|
||||||
const USER_NO_DEKEY: &str = "You haven't set up a decoupled encryption key or it's not available.";
|
const USER_NO_DEKEY: &str = "You haven't set up a decoupled encryption key or it's not available.";
|
||||||
@@ -439,12 +439,52 @@ impl Room {
|
|||||||
let content: String = content.into();
|
let content: String = content.into();
|
||||||
let replies: Vec<EventId> = replies.into_iter().collect();
|
let replies: Vec<EventId> = replies.into_iter().collect();
|
||||||
|
|
||||||
let persons = PersonRegistry::global(cx);
|
// Get current user's public key
|
||||||
let nostr = NostrRegistry::global(cx);
|
let nostr = NostrRegistry::global(cx);
|
||||||
|
let sender = nostr.read(cx).current_user()?;
|
||||||
|
|
||||||
|
// Construct a direct message rumor event
|
||||||
|
// WARNING: never sign and send this event to relays
|
||||||
|
let mut event = EventBuilder::new(kind, content)
|
||||||
|
.tags(self.conversation_tags(&replies, sender, cx))
|
||||||
|
.finalize_unsigned(sender);
|
||||||
|
|
||||||
|
// Ensure that the ID is set
|
||||||
|
event.ensure_id();
|
||||||
|
|
||||||
|
Some(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct a rumor event for an encrypted file message (NIP-17 kind 15)
|
||||||
|
pub fn file_rumor<I>(&self, file: FileAttachment, replies: I, cx: &App) -> Option<UnsignedEvent>
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = EventId>,
|
||||||
|
{
|
||||||
|
let replies: Vec<EventId> = replies.into_iter().collect();
|
||||||
|
|
||||||
// Get current user's public key
|
// Get current user's public key
|
||||||
|
let nostr = NostrRegistry::global(cx);
|
||||||
let sender = nostr.read(cx).current_user()?;
|
let sender = nostr.read(cx).current_user()?;
|
||||||
|
|
||||||
|
let mut tags = self.conversation_tags(&replies, sender, cx);
|
||||||
|
tags.extend(file.tags());
|
||||||
|
|
||||||
|
// Construct a file message rumor event
|
||||||
|
// WARNING: never sign and send this event to relays
|
||||||
|
let mut event = EventBuilder::new(KIND_FILE_MESSAGE, file.url.to_string())
|
||||||
|
.tags(tags)
|
||||||
|
.finalize_unsigned(sender);
|
||||||
|
|
||||||
|
// Ensure that the ID is set
|
||||||
|
event.ensure_id();
|
||||||
|
|
||||||
|
Some(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the `subject` + reply `e` tags + receiver `p` tags (excluding `sender`)
|
||||||
|
fn conversation_tags(&self, replies: &[EventId], sender: PublicKey, cx: &App) -> Vec<Tag> {
|
||||||
|
let persons = PersonRegistry::global(cx);
|
||||||
|
|
||||||
// Construct event's tags
|
// Construct event's tags
|
||||||
let mut tags = vec![];
|
let mut tags = vec![];
|
||||||
|
|
||||||
@@ -454,8 +494,8 @@ impl Room {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add all reply tags
|
// Add all reply tags
|
||||||
for id in replies.into_iter() {
|
for id in replies {
|
||||||
tags.push(Tag::event(id))
|
tags.push(Tag::event(*id))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add all receiver tags (no intermediate allocation)
|
// Add all receiver tags (no intermediate allocation)
|
||||||
@@ -467,16 +507,7 @@ impl Room {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Construct a direct message rumor event
|
tags
|
||||||
// WARNING: never sign and send this event to relays
|
|
||||||
let mut event = EventBuilder::new(kind, content)
|
|
||||||
.tags(tags)
|
|
||||||
.finalize_unsigned(sender);
|
|
||||||
|
|
||||||
// Ensure that the ID is set
|
|
||||||
event.ensure_id();
|
|
||||||
|
|
||||||
Some(event)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Select the appropriate signer based on signer kind and available keys.
|
/// Select the appropriate signer based on signer kind and available keys.
|
||||||
@@ -609,7 +640,7 @@ async fn send_gift_wrap(
|
|||||||
rumor: &UnsignedEvent,
|
rumor: &UnsignedEvent,
|
||||||
config: &SignerKind,
|
config: &SignerKind,
|
||||||
) -> Result<SendReport, Error> {
|
) -> Result<SendReport, Error> {
|
||||||
let k_tag = Tag::custom("k", vec!["14"]);
|
let k_tag = Tag::custom("k", [rumor.kind.to_string()]);
|
||||||
let mut extra_tags = vec![k_tag];
|
let mut extra_tags = vec![k_tag];
|
||||||
|
|
||||||
// Determine the receiver public key based on the config
|
// Determine the receiver public key based on the config
|
||||||
|
|||||||
Reference in New Issue
Block a user