mirror of
https://github.com/mikedilger/gossip.git
synced 2024-09-29 08:21:47 +00:00
Merge branch 'dmchat' into unstable
This commit is contained in:
commit
90913c7270
@ -1,3 +1,4 @@
|
||||
use crate::dm_channel::DmChannel;
|
||||
use nostr_types::{
|
||||
Event, Id, IdHex, Metadata, MilliSatoshi, PublicKey, RelayUrl, Tag, UncheckedUrl,
|
||||
};
|
||||
@ -28,7 +29,7 @@ pub enum ToOverlordMessage {
|
||||
MinionJobComplete(RelayUrl, u64),
|
||||
MinionJobUpdated(RelayUrl, u64, u64),
|
||||
PickRelays,
|
||||
Post(String, Vec<Tag>, Option<Id>),
|
||||
Post(String, Vec<Tag>, Option<Id>, Option<DmChannel>),
|
||||
PruneCache,
|
||||
PruneDatabase,
|
||||
PullFollow,
|
||||
|
@ -1,4 +1,5 @@
|
||||
use nostr_types::{PublicKey, Unixtime};
|
||||
use crate::globals::GLOBALS;
|
||||
use nostr_types::{Event, EventKind, PublicKey, Unixtime};
|
||||
use sha2::Digest;
|
||||
|
||||
/// This represents a DM (direct message) channel which includes a set
|
||||
@ -46,6 +47,57 @@ impl DmChannel {
|
||||
}
|
||||
hex::encode(hasher.finalize())
|
||||
}
|
||||
|
||||
pub fn from_event(event: &Event, my_pubkey: Option<PublicKey>) -> Option<DmChannel> {
|
||||
let my_pubkey = match my_pubkey {
|
||||
Some(pk) => pk,
|
||||
None => match GLOBALS.signer.public_key() {
|
||||
Some(pk) => pk,
|
||||
None => return None,
|
||||
},
|
||||
};
|
||||
|
||||
if event.kind == EventKind::EncryptedDirectMessage {
|
||||
let mut people: Vec<PublicKey> = event
|
||||
.people()
|
||||
.iter()
|
||||
.filter_map(|(pk, _, _)| PublicKey::try_from(pk).ok())
|
||||
.collect();
|
||||
people.push(event.pubkey);
|
||||
people.retain(|p| *p != my_pubkey);
|
||||
if people.len() > 1 {
|
||||
None
|
||||
} else {
|
||||
Some(Self::new(&people))
|
||||
}
|
||||
} else if event.kind == EventKind::GiftWrap {
|
||||
if let Ok(rumor) = GLOBALS.signer.unwrap_giftwrap(event) {
|
||||
let rumor_event = rumor.into_event_with_bad_signature();
|
||||
let mut people: Vec<PublicKey> = rumor_event
|
||||
.people()
|
||||
.iter()
|
||||
.filter_map(|(pk, _, _)| PublicKey::try_from(pk).ok())
|
||||
.collect();
|
||||
people.push(rumor_event.pubkey); // include author too
|
||||
people.retain(|p| *p != my_pubkey);
|
||||
Some(Self::new(&people))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else if event.kind == EventKind::DmChat {
|
||||
// unwrapped rumor
|
||||
let mut people: Vec<PublicKey> = event
|
||||
.people()
|
||||
.iter()
|
||||
.filter_map(|(pk, _, _)| PublicKey::try_from(pk).ok())
|
||||
.collect();
|
||||
people.push(event.pubkey); // include author too
|
||||
people.retain(|p| *p != my_pubkey);
|
||||
Some(Self::new(&people))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
|
@ -8,6 +8,7 @@ pub enum ErrorKind {
|
||||
Empty(String),
|
||||
EventNotFound,
|
||||
General(String),
|
||||
GroupDmsNotYetSupported,
|
||||
HttpError(http::Error),
|
||||
JoinError(tokio::task::JoinError),
|
||||
Lmdb(heed::Error),
|
||||
@ -65,6 +66,7 @@ impl std::fmt::Display for Error {
|
||||
Delegation(s) => write!(f, "NIP-26 Delegation Error: {s}"),
|
||||
Empty(s) => write!(f, "{s} is empty"),
|
||||
EventNotFound => write!(f, "Event not found"),
|
||||
GroupDmsNotYetSupported => write!(f, "Group DMs are not yet supported"),
|
||||
General(s) => write!(f, "{s}"),
|
||||
HttpError(e) => write!(f, "HTTP error: {e}"),
|
||||
JoinError(e) => write!(f, "Task join error: {e}"),
|
||||
|
@ -4,6 +4,7 @@ use crate::comms::{
|
||||
RelayConnectionReason, RelayJob, ToMinionMessage, ToMinionPayload, ToMinionPayloadDetail,
|
||||
ToOverlordMessage,
|
||||
};
|
||||
use crate::dm_channel::DmChannel;
|
||||
use crate::error::{Error, ErrorKind};
|
||||
use crate::globals::{ZapState, GLOBALS};
|
||||
use crate::people::Person;
|
||||
@ -709,8 +710,8 @@ impl Overlord {
|
||||
count
|
||||
));
|
||||
}
|
||||
ToOverlordMessage::Post(content, tags, reply_to) => {
|
||||
self.post(content, tags, reply_to).await?;
|
||||
ToOverlordMessage::Post(content, tags, reply_to, dm_channel) => {
|
||||
self.post(content, tags, reply_to, dm_channel).await?;
|
||||
}
|
||||
ToOverlordMessage::PullFollow => {
|
||||
self.pull_following().await?;
|
||||
@ -935,155 +936,181 @@ impl Overlord {
|
||||
content: String,
|
||||
mut tags: Vec<Tag>,
|
||||
reply_to: Option<Id>,
|
||||
dm_channel: Option<DmChannel>,
|
||||
) -> Result<(), Error> {
|
||||
// We will fill this just before we create the event
|
||||
let mut tagged_pubkeys: Vec<PublicKey>;
|
||||
let public_key = match GLOBALS.signer.public_key() {
|
||||
Some(pk) => pk,
|
||||
None => {
|
||||
tracing::warn!("No public key! Not posting");
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
let event = {
|
||||
let public_key = match GLOBALS.signer.public_key() {
|
||||
Some(pk) => pk,
|
||||
None => {
|
||||
tracing::warn!("No public key! Not posting");
|
||||
return Ok(());
|
||||
let pre_event = match dm_channel {
|
||||
Some(dmc) => {
|
||||
if dmc.keys().len() > 1 {
|
||||
return Err((ErrorKind::GroupDmsNotYetSupported, file!(), line!()).into());
|
||||
}
|
||||
};
|
||||
|
||||
if GLOBALS.storage.read_setting_set_client_tag() {
|
||||
tags.push(Tag::Other {
|
||||
tag: "client".to_owned(),
|
||||
data: vec!["gossip".to_owned()],
|
||||
});
|
||||
}
|
||||
|
||||
// Add Tags based on references in the content
|
||||
//
|
||||
// FIXME - this function takes a 'tags' variable. We may want to let
|
||||
// the user determine which tags to keep and which to delete, so we
|
||||
// should probably move this processing into the post editor instead.
|
||||
// For now, I'm just trying to remove the old #[0] type substitutions
|
||||
// and use the new NostrBech32 parsing.
|
||||
for bech32 in NostrBech32::find_all_in_string(&content).iter() {
|
||||
match bech32 {
|
||||
NostrBech32::EventAddr(ea) => {
|
||||
add_addr_to_tags(
|
||||
&mut tags,
|
||||
ea.kind,
|
||||
ea.author.into(),
|
||||
ea.d.clone(),
|
||||
ea.relays.get(0).cloned(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
NostrBech32::EventPointer(ep) => {
|
||||
// NIP-10: "Those marked with "mention" denote a quoted or reposted event id."
|
||||
add_event_to_tags(&mut tags, ep.id, "mention").await;
|
||||
}
|
||||
NostrBech32::Id(id) => {
|
||||
// NIP-10: "Those marked with "mention" denote a quoted or reposted event id."
|
||||
add_event_to_tags(&mut tags, *id, "mention").await;
|
||||
}
|
||||
NostrBech32::Profile(prof) => {
|
||||
add_pubkey_to_tags(&mut tags, &prof.pubkey).await;
|
||||
}
|
||||
NostrBech32::Pubkey(pk) => {
|
||||
add_pubkey_to_tags(&mut tags, pk).await;
|
||||
}
|
||||
NostrBech32::Relay(_) => {
|
||||
// we don't need to add this to tags I don't think.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Standardize nostr links (prepend 'nostr:' where missing)
|
||||
// (This was a bad idea to do this late in the process, it breaks links that contain
|
||||
// nostr urls)
|
||||
// content = NostrUrl::urlize(&content);
|
||||
|
||||
// Find and tag all hashtags
|
||||
for capture in GLOBALS.hashtag_regex.captures_iter(&content) {
|
||||
tags.push(Tag::Hashtag {
|
||||
hashtag: capture[1][1..].to_string(),
|
||||
trailing: Vec::new(),
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(parent_id) = reply_to {
|
||||
// Get the event we are replying to
|
||||
let parent = match GLOBALS.storage.read_event(parent_id)? {
|
||||
Some(e) => e,
|
||||
None => return Err("Cannot find event we are replying to.".into()),
|
||||
let recipient = if dmc.keys().is_empty() {
|
||||
public_key // must be to yourself
|
||||
} else {
|
||||
dmc.keys()[0]
|
||||
};
|
||||
|
||||
// Add a 'p' tag for the author we are replying to (except if it is our own key)
|
||||
if parent.pubkey != public_key {
|
||||
add_pubkey_to_tags(&mut tags, &parent.pubkey).await;
|
||||
// On a DM, we ignore tags and reply_to
|
||||
|
||||
GLOBALS.signer.new_nip04(recipient, &content)?
|
||||
}
|
||||
_ => {
|
||||
if GLOBALS.storage.read_setting_set_client_tag() {
|
||||
tags.push(Tag::Other {
|
||||
tag: "client".to_owned(),
|
||||
data: vec!["gossip".to_owned()],
|
||||
});
|
||||
}
|
||||
|
||||
// Add all the 'p' tags from the note we are replying to (except our own)
|
||||
// FIXME: Should we avoid taging people who are muted?
|
||||
for tag in &parent.tags {
|
||||
if let Tag::Pubkey { pubkey, .. } = tag {
|
||||
if pubkey.as_str() != public_key.as_hex_string() {
|
||||
add_pubkey_hex_to_tags(&mut tags, pubkey).await;
|
||||
// Add Tags based on references in the content
|
||||
//
|
||||
// FIXME - this function takes a 'tags' variable. We may want to let
|
||||
// the user determine which tags to keep and which to delete, so we
|
||||
// should probably move this processing into the post editor instead.
|
||||
// For now, I'm just trying to remove the old #[0] type substitutions
|
||||
// and use the new NostrBech32 parsing.
|
||||
for bech32 in NostrBech32::find_all_in_string(&content).iter() {
|
||||
match bech32 {
|
||||
NostrBech32::EventAddr(ea) => {
|
||||
add_addr_to_tags(
|
||||
&mut tags,
|
||||
ea.kind,
|
||||
ea.author.into(),
|
||||
ea.d.clone(),
|
||||
ea.relays.get(0).cloned(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
NostrBech32::EventPointer(ep) => {
|
||||
// NIP-10: "Those marked with "mention" denote a quoted or reposted event id."
|
||||
add_event_to_tags(&mut tags, ep.id, "mention").await;
|
||||
}
|
||||
NostrBech32::Id(id) => {
|
||||
// NIP-10: "Those marked with "mention" denote a quoted or reposted event id."
|
||||
add_event_to_tags(&mut tags, *id, "mention").await;
|
||||
}
|
||||
NostrBech32::Profile(prof) => {
|
||||
if dm_channel.is_none() {
|
||||
add_pubkey_to_tags(&mut tags, &prof.pubkey).await;
|
||||
}
|
||||
}
|
||||
NostrBech32::Pubkey(pk) => {
|
||||
if dm_channel.is_none() {
|
||||
add_pubkey_to_tags(&mut tags, pk).await;
|
||||
}
|
||||
}
|
||||
NostrBech32::Relay(_) => {
|
||||
// we don't need to add this to tags I don't think.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((root, _maybeurl)) = parent.replies_to_root() {
|
||||
// Add an 'e' tag for the root
|
||||
add_event_to_tags(&mut tags, root, "root").await;
|
||||
// Standardize nostr links (prepend 'nostr:' where missing)
|
||||
// (This was a bad idea to do this late in the process, it breaks links that contain
|
||||
// nostr urls)
|
||||
// content = NostrUrl::urlize(&content);
|
||||
|
||||
// Find and tag all hashtags
|
||||
for capture in GLOBALS.hashtag_regex.captures_iter(&content) {
|
||||
tags.push(Tag::Hashtag {
|
||||
hashtag: capture[1][1..].to_string(),
|
||||
trailing: Vec::new(),
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(parent_id) = reply_to {
|
||||
// Get the event we are replying to
|
||||
let parent = match GLOBALS.storage.read_event(parent_id)? {
|
||||
Some(e) => e,
|
||||
None => return Err("Cannot find event we are replying to.".into()),
|
||||
};
|
||||
|
||||
// Add a 'p' tag for the author we are replying to (except if it is our own key)
|
||||
if parent.pubkey != public_key {
|
||||
if dm_channel.is_none() {
|
||||
add_pubkey_to_tags(&mut tags, &parent.pubkey).await;
|
||||
}
|
||||
}
|
||||
|
||||
// Add all the 'p' tags from the note we are replying to (except our own)
|
||||
// FIXME: Should we avoid taging people who are muted?
|
||||
if dm_channel.is_none() {
|
||||
for tag in &parent.tags {
|
||||
if let Tag::Pubkey { pubkey, .. } = tag {
|
||||
if pubkey.as_str() != public_key.as_hex_string() {
|
||||
add_pubkey_hex_to_tags(&mut tags, pubkey).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((root, _maybeurl)) = parent.replies_to_root() {
|
||||
// Add an 'e' tag for the root
|
||||
add_event_to_tags(&mut tags, root, "root").await;
|
||||
|
||||
// Add an 'e' tag for the note we are replying to
|
||||
add_event_to_tags(&mut tags, parent_id, "reply").await;
|
||||
} else {
|
||||
let ancestors = parent.referred_events();
|
||||
if ancestors.is_empty() {
|
||||
// parent is the root
|
||||
add_event_to_tags(&mut tags, parent_id, "root").await;
|
||||
} else {
|
||||
// Add an 'e' tag for the note we are replying to
|
||||
// (and we don't know about the root, the parent is malformed).
|
||||
add_event_to_tags(&mut tags, parent_id, "reply").await;
|
||||
} else {
|
||||
let ancestors = parent.referred_events();
|
||||
if ancestors.is_empty() {
|
||||
// parent is the root
|
||||
add_event_to_tags(&mut tags, parent_id, "root").await;
|
||||
} else {
|
||||
// Add an 'e' tag for the note we are replying to
|
||||
// (and we don't know about the root, the parent is malformed).
|
||||
add_event_to_tags(&mut tags, parent_id, "reply").await;
|
||||
}
|
||||
}
|
||||
|
||||
// Possibly propagate a subject tag
|
||||
for tag in &parent.tags {
|
||||
if let Tag::Subject { subject, .. } = tag {
|
||||
let mut subject = subject.to_owned();
|
||||
if !subject.starts_with("Re: ") {
|
||||
subject = format!("Re: {}", subject);
|
||||
}
|
||||
subject = subject.chars().take(80).collect();
|
||||
add_subject_to_tags_if_missing(&mut tags, subject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Possibly propagate a subject tag
|
||||
for tag in &parent.tags {
|
||||
if let Tag::Subject { subject, .. } = tag {
|
||||
let mut subject = subject.to_owned();
|
||||
if !subject.starts_with("Re: ") {
|
||||
subject = format!("Re: {}", subject);
|
||||
}
|
||||
subject = subject.chars().take(80).collect();
|
||||
add_subject_to_tags_if_missing(&mut tags, subject);
|
||||
}
|
||||
PreEvent {
|
||||
pubkey: public_key,
|
||||
created_at: Unixtime::now().unwrap(),
|
||||
kind: EventKind::TextNote,
|
||||
tags,
|
||||
content,
|
||||
ots: None,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Copy the tagged pubkeys for determine which relays to send to
|
||||
tagged_pubkeys = tags
|
||||
.iter()
|
||||
.filter_map(|t| {
|
||||
if let Tag::Pubkey { pubkey, .. } = t {
|
||||
match PublicKey::try_from_hex_string(pubkey, true) {
|
||||
Ok(pk) => Some(pk),
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
// Copy the tagged pubkeys for determine which relays to send to
|
||||
let mut tagged_pubkeys: Vec<PublicKey> = pre_event
|
||||
.tags
|
||||
.iter()
|
||||
.filter_map(|t| {
|
||||
if let Tag::Pubkey { pubkey, .. } = t {
|
||||
match PublicKey::try_from_hex_string(pubkey, true) {
|
||||
Ok(pk) => Some(pk),
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let pre_event = PreEvent {
|
||||
pubkey: public_key,
|
||||
created_at: Unixtime::now().unwrap(),
|
||||
kind: EventKind::TextNote,
|
||||
tags,
|
||||
content,
|
||||
ots: None,
|
||||
};
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let event = {
|
||||
let powint = GLOBALS.storage.read_setting_pow();
|
||||
let pow = if powint > 0 { Some(powint) } else { None };
|
||||
let (work_sender, work_receiver) = mpsc::channel();
|
||||
|
@ -197,6 +197,17 @@ impl Signer {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_nip04(
|
||||
&self,
|
||||
recipient_public_key: PublicKey,
|
||||
message: &str,
|
||||
) -> Result<PreEvent, Error> {
|
||||
match &*self.private.read() {
|
||||
Some(pk) => Ok(PreEvent::new_nip04(pk, recipient_public_key, message)?),
|
||||
_ => Err((ErrorKind::NoPrivateKey, file!(), line!()).into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn export_private_key_bech32(&self, pass: &str) -> Result<String, Error> {
|
||||
let maybe_encrypted = self.encrypted.read().to_owned();
|
||||
match maybe_encrypted {
|
||||
|
@ -2324,27 +2324,9 @@ impl Storage {
|
||||
let unread = 1 - self.is_event_viewed(event.id)? as usize;
|
||||
if event.kind == EventKind::EncryptedDirectMessage {
|
||||
let time = event.created_at;
|
||||
let dmchannel = {
|
||||
if event.pubkey != my_pubkey {
|
||||
// DM sent to me
|
||||
DmChannel::new(&[event.pubkey])
|
||||
} else {
|
||||
// DM sent from me
|
||||
let mut maybe_channel: Option<DmChannel> = None;
|
||||
for tag in event.tags.iter() {
|
||||
if let Tag::Pubkey { pubkey, .. } = tag {
|
||||
if let Ok(pk) = PublicKey::try_from(pubkey) {
|
||||
if pk != my_pubkey {
|
||||
maybe_channel = Some(DmChannel::new(&[pk]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
match maybe_channel {
|
||||
Some(dmchannel) => dmchannel,
|
||||
None => continue,
|
||||
}
|
||||
}
|
||||
let dmchannel = match DmChannel::from_event(event, Some(my_pubkey)) {
|
||||
Some(dmc) => dmc,
|
||||
None => continue,
|
||||
};
|
||||
map.entry(dmchannel.clone())
|
||||
.and_modify(|d| {
|
||||
@ -2362,15 +2344,9 @@ impl Storage {
|
||||
if let Ok(rumor) = GLOBALS.signer.unwrap_giftwrap(event) {
|
||||
let rumor_event = rumor.into_event_with_bad_signature();
|
||||
let time = rumor_event.created_at;
|
||||
let dmchannel = {
|
||||
let mut people: Vec<PublicKey> = rumor_event
|
||||
.people()
|
||||
.iter()
|
||||
.filter_map(|(pk, _, _)| PublicKey::try_from(pk).ok())
|
||||
.filter(|pk| *pk != my_pubkey)
|
||||
.collect();
|
||||
people.push(rumor_event.pubkey); // include author too
|
||||
DmChannel::new(&people)
|
||||
let dmchannel = match DmChannel::from_event(&rumor_event, Some(my_pubkey)) {
|
||||
Some(dmc) => dmc,
|
||||
None => continue,
|
||||
};
|
||||
map.entry(dmchannel.clone())
|
||||
.and_modify(|d| {
|
||||
@ -2400,61 +2376,25 @@ impl Storage {
|
||||
None => return Ok(Vec::new()),
|
||||
};
|
||||
|
||||
let mut pass1 = self.find_events(
|
||||
let mut output: Vec<Event> = self.find_events(
|
||||
&[EventKind::EncryptedDirectMessage, EventKind::GiftWrap],
|
||||
&[],
|
||||
Some(Unixtime(0)),
|
||||
|event| {
|
||||
if event.kind == EventKind::EncryptedDirectMessage {
|
||||
if channel.keys().len() > 1 { return false; }
|
||||
if channel.keys().len() == 0 { return true; } // self-channel
|
||||
let other = &channel.keys()[0];
|
||||
let people = event.people();
|
||||
match people.len() {
|
||||
1 => (event.pubkey == my_pubkey && event.is_tagged(other))
|
||||
|| (event.pubkey == *other && event.is_tagged(&my_pubkey)),
|
||||
2 => (event.pubkey == my_pubkey || event.pubkey == *other)
|
||||
&& (event.is_tagged(&my_pubkey) && event.is_tagged(other)),
|
||||
_ => false,
|
||||
if let Some(event_dm_channel) = DmChannel::from_event(event, Some(my_pubkey)) {
|
||||
if event_dm_channel == *channel {
|
||||
return true;
|
||||
}
|
||||
} else if event.kind == EventKind::GiftWrap {
|
||||
// Decrypt in next pass, else we would have to decrypt twice
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
false
|
||||
},
|
||||
false,
|
||||
)?;
|
||||
|
||||
let mut pass2: Vec<Event> = Vec::new();
|
||||
|
||||
for event in pass1.drain(..) {
|
||||
if event.kind == EventKind::EncryptedDirectMessage {
|
||||
pass2.push(event); // already validated
|
||||
} else if event.kind == EventKind::GiftWrap {
|
||||
if let Ok(rumor) = GLOBALS.signer.unwrap_giftwrap(&event) {
|
||||
let mut rumor_event = rumor.into_event_with_bad_signature();
|
||||
rumor_event.id = event.id; // lie, so it indexes it under the giftwrap
|
||||
let mut tagged: Vec<PublicKey> = rumor_event
|
||||
.people()
|
||||
.drain(..)
|
||||
.filter_map(|(pkh, _, _)| PublicKey::try_from(pkh).ok())
|
||||
.collect();
|
||||
tagged.push(rumor_event.pubkey); // include author
|
||||
tagged.retain(|pk| *pk != my_pubkey); // never include user
|
||||
let this_channel = DmChannel::new(&tagged);
|
||||
if this_channel == *channel {
|
||||
pass2.push(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sort
|
||||
pass2.sort_by(|a, b| b.created_at.cmp(&a.created_at));
|
||||
output.sort_by(|a, b| b.created_at.cmp(&a.created_at));
|
||||
|
||||
Ok(pass2.iter().map(|e| e.id).collect())
|
||||
Ok(output.iter().map(|e| e.id).collect())
|
||||
}
|
||||
|
||||
pub fn rebuild_event_indices(&self) -> Result<(), Error> {
|
||||
|
@ -16,7 +16,6 @@ pub(super) fn update(app: &mut GossipUi, _ctx: &Context, _frame: &mut eframe::Fr
|
||||
|
||||
ui.heading("Direct Private Message Channels");
|
||||
ui.add_space(12.0);
|
||||
ui.separator();
|
||||
|
||||
ScrollArea::vertical()
|
||||
.id_source("dm_chat_list")
|
||||
|
@ -8,6 +8,7 @@ use super::notedata::{NoteData, RepostType};
|
||||
|
||||
use super::FeedNoteParams;
|
||||
use crate::comms::ToOverlordMessage;
|
||||
use crate::dm_channel::DmChannel;
|
||||
use crate::feed::FeedKind;
|
||||
use crate::globals::{ZapState, GLOBALS};
|
||||
use crate::ui::widgets::CopyButton;
|
||||
@ -615,61 +616,65 @@ fn render_note_inner(
|
||||
|
||||
ui.add_space(24.0);
|
||||
|
||||
if render_data.can_post
|
||||
&& note.event.kind != EventKind::EncryptedDirectMessage
|
||||
{
|
||||
// Button to Repost
|
||||
if ui
|
||||
.add(
|
||||
Label::new(RichText::new("↻").size(18.0))
|
||||
.sense(Sense::click()),
|
||||
)
|
||||
.on_hover_text("Repost")
|
||||
.clicked()
|
||||
if render_data.can_post {
|
||||
if note.event.kind != EventKind::EncryptedDirectMessage
|
||||
&& note.event.kind != EventKind::DmChat
|
||||
{
|
||||
app.draft_repost = Some(note.event.id);
|
||||
app.replying_to = None;
|
||||
app.show_post_area = true;
|
||||
}
|
||||
|
||||
ui.add_space(24.0);
|
||||
|
||||
// Button to quote note
|
||||
if ui
|
||||
.add(
|
||||
Label::new(RichText::new("“…”").size(18.0))
|
||||
.sense(Sense::click()),
|
||||
)
|
||||
.on_hover_text("Quote")
|
||||
.clicked()
|
||||
{
|
||||
if !app.draft.ends_with(' ') && !app.draft.is_empty() {
|
||||
app.draft.push(' ');
|
||||
// Button to Repost
|
||||
if ui
|
||||
.add(
|
||||
Label::new(RichText::new("↻").size(18.0))
|
||||
.sense(Sense::click()),
|
||||
)
|
||||
.on_hover_text("Repost")
|
||||
.clicked()
|
||||
{
|
||||
app.draft_repost = Some(note.event.id);
|
||||
app.replying_to = None;
|
||||
app.draft_dm_channel = None;
|
||||
app.show_post_area = true;
|
||||
}
|
||||
let event_pointer = EventPointer {
|
||||
id: note.event.id,
|
||||
relays: match GLOBALS
|
||||
.storage
|
||||
.get_event_seen_on_relay(note.event.id)
|
||||
{
|
||||
Err(_) => vec![],
|
||||
Ok(vec) => vec
|
||||
.iter()
|
||||
.map(|(url, _)| url.to_unchecked_url())
|
||||
.collect(),
|
||||
},
|
||||
author: None,
|
||||
kind: None,
|
||||
};
|
||||
let nostr_url: NostrUrl = event_pointer.into();
|
||||
app.draft.push_str(&format!("{}", nostr_url));
|
||||
app.draft_repost = None;
|
||||
app.replying_to = None;
|
||||
app.show_post_area = true;
|
||||
app.draft_needs_focus = true;
|
||||
}
|
||||
|
||||
ui.add_space(24.0);
|
||||
ui.add_space(24.0);
|
||||
|
||||
// Button to quote note
|
||||
if ui
|
||||
.add(
|
||||
Label::new(RichText::new("“…”").size(18.0))
|
||||
.sense(Sense::click()),
|
||||
)
|
||||
.on_hover_text("Quote")
|
||||
.clicked()
|
||||
{
|
||||
if !app.draft.ends_with(' ') && !app.draft.is_empty() {
|
||||
app.draft.push(' ');
|
||||
}
|
||||
let event_pointer = EventPointer {
|
||||
id: note.event.id,
|
||||
relays: match GLOBALS
|
||||
.storage
|
||||
.get_event_seen_on_relay(note.event.id)
|
||||
{
|
||||
Err(_) => vec![],
|
||||
Ok(vec) => vec
|
||||
.iter()
|
||||
.map(|(url, _)| url.to_unchecked_url())
|
||||
.collect(),
|
||||
},
|
||||
author: None,
|
||||
kind: None,
|
||||
};
|
||||
let nostr_url: NostrUrl = event_pointer.into();
|
||||
app.draft.push_str(&format!("{}", nostr_url));
|
||||
app.draft_repost = None;
|
||||
app.replying_to = None;
|
||||
app.draft_dm_channel = None;
|
||||
app.show_post_area = true;
|
||||
app.draft_needs_focus = true;
|
||||
}
|
||||
|
||||
ui.add_space(24.0);
|
||||
}
|
||||
|
||||
// Button to reply
|
||||
if ui
|
||||
@ -683,6 +688,8 @@ fn render_note_inner(
|
||||
app.replying_to = Some(note.event.id);
|
||||
app.draft_repost = None;
|
||||
app.show_post_area = true;
|
||||
app.draft_dm_channel =
|
||||
DmChannel::from_event(¬e.event, None);
|
||||
app.draft_needs_focus = true;
|
||||
}
|
||||
|
||||
|
@ -163,6 +163,12 @@ fn real_posting_area(app: &mut GossipUi, ctx: &Context, frame: &mut eframe::Fram
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(dm_channel) = &app.draft_dm_channel {
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(format!("DIRECT MESSAGE TO: {}", dm_channel.name()));
|
||||
});
|
||||
}
|
||||
|
||||
let draft_response = ui.add(
|
||||
text_edit_multiline!(app, app.draft)
|
||||
.id_source("compose_area")
|
||||
@ -296,6 +302,7 @@ fn real_posting_area(app: &mut GossipUi, ctx: &Context, frame: &mut eframe::Fram
|
||||
app.draft.clone(),
|
||||
tags,
|
||||
Some(replying_to_id),
|
||||
app.draft_dm_channel.clone(),
|
||||
));
|
||||
}
|
||||
None => {
|
||||
@ -308,6 +315,7 @@ fn real_posting_area(app: &mut GossipUi, ctx: &Context, frame: &mut eframe::Fram
|
||||
app.draft.clone(),
|
||||
tags,
|
||||
None,
|
||||
app.draft_dm_channel.clone(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ mod you;
|
||||
|
||||
use crate::about::About;
|
||||
use crate::comms::ToOverlordMessage;
|
||||
use crate::dm_channel::DmChannel;
|
||||
use crate::error::Error;
|
||||
use crate::feed::FeedKind;
|
||||
use crate::globals::{ZapState, GLOBALS};
|
||||
@ -243,6 +244,7 @@ struct GossipUi {
|
||||
subject: String,
|
||||
include_content_warning: bool,
|
||||
content_warning: String,
|
||||
draft_dm_channel: Option<DmChannel>,
|
||||
replying_to: Option<Id>,
|
||||
|
||||
// User entry: metadata
|
||||
@ -460,6 +462,7 @@ impl GossipUi {
|
||||
subject: "".to_owned(),
|
||||
include_content_warning: false,
|
||||
content_warning: "".to_owned(),
|
||||
draft_dm_channel: None,
|
||||
replying_to: None,
|
||||
editing_metadata: false,
|
||||
metadata: Metadata::new(),
|
||||
@ -551,6 +554,7 @@ impl GossipUi {
|
||||
self.include_subject = false;
|
||||
self.subject = "".to_owned();
|
||||
self.replying_to = None;
|
||||
self.draft_dm_channel = None;
|
||||
self.include_content_warning = false;
|
||||
self.content_warning = "".to_owned();
|
||||
}
|
||||
@ -802,6 +806,11 @@ impl eframe::App for GossipUi {
|
||||
let response = ui.add_sized([crate::AVATAR_SIZE_F32, crate::AVATAR_SIZE_F32], egui::Button::new(text.color(self.settings.theme.navigation_text_color())).stroke(egui::Stroke::NONE).rounding(egui::Rounding::same(crate::AVATAR_SIZE_F32)).fill(self.settings.theme.navigation_bg_fill()));
|
||||
if response.clicked() {
|
||||
self.show_post_area = true;
|
||||
if let Page::Feed(FeedKind::DmChat(channel)) = &self.page {
|
||||
self.draft_dm_channel = Some(channel.clone());
|
||||
} else {
|
||||
self.draft_dm_channel = None;
|
||||
}
|
||||
if GLOBALS.signer.is_ready() {
|
||||
self.draft_needs_focus = true;
|
||||
} else {
|
||||
@ -947,6 +956,15 @@ impl GossipUi {
|
||||
app.set_page(Page::Feed(FeedKind::Person(person.pubkey)));
|
||||
}
|
||||
}
|
||||
if GLOBALS.signer.is_ready() {
|
||||
if ui.button("Send DM").clicked() {
|
||||
app.replying_to = None;
|
||||
app.draft_repost = None;
|
||||
app.show_post_area = true;
|
||||
app.draft_dm_channel = Some(DmChannel::new(&[person.pubkey]));
|
||||
app.draft_needs_focus = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if person.followed {
|
||||
|
@ -73,12 +73,14 @@ macro_rules! theme_dispatch {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn highlight_color(&self) -> Color32 {
|
||||
match self.variant {
|
||||
$( $variant => $class::highlight_color(self.dark_mode), )+
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn accent_complementary_color(&self) -> Color32 {
|
||||
match self.variant {
|
||||
$( $variant => $class::accent_complementary_color(self.dark_mode), )+
|
||||
|
Loading…
Reference in New Issue
Block a user