mirror of
https://github.com/mikedilger/gossip.git
synced 2024-09-29 16:31:18 +00:00
Merge remote-tracking branch 'dilger/unstable' into feature/profiles-ui-restyle
This commit is contained in:
commit
7dd1838cc7
@ -3,12 +3,14 @@ use crate::AVATAR_SIZE_F32;
|
|||||||
use eframe::egui;
|
use eframe::egui;
|
||||||
use egui::{Context, Image, RichText, Sense, Ui, Vec2};
|
use egui::{Context, Image, RichText, Sense, Ui, Vec2};
|
||||||
use gossip_lib::comms::ToOverlordMessage;
|
use gossip_lib::comms::ToOverlordMessage;
|
||||||
use gossip_lib::Person;
|
use gossip_lib::{Person, PersonList, GLOBALS};
|
||||||
use gossip_lib::GLOBALS;
|
|
||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
|
|
||||||
pub(super) fn update(app: &mut GossipUi, ctx: &Context, _frame: &mut eframe::Frame, ui: &mut Ui) {
|
pub(super) fn update(app: &mut GossipUi, ctx: &Context, _frame: &mut eframe::Frame, ui: &mut Ui) {
|
||||||
let followed_pubkeys = GLOBALS.people.get_followed_pubkeys();
|
let followed_pubkeys = GLOBALS
|
||||||
|
.storage
|
||||||
|
.get_people_in_list(PersonList::Followed, None)
|
||||||
|
.unwrap_or(vec![]);
|
||||||
let mut people: Vec<Person> = Vec::new();
|
let mut people: Vec<Person> = Vec::new();
|
||||||
for pk in &followed_pubkeys {
|
for pk in &followed_pubkeys {
|
||||||
if let Ok(Some(person)) = GLOBALS.storage.read_person(pk) {
|
if let Ok(Some(person)) = GLOBALS.storage.read_person(pk) {
|
||||||
@ -23,16 +25,15 @@ pub(super) fn update(app: &mut GossipUi, ctx: &Context, _frame: &mut eframe::Fra
|
|||||||
|
|
||||||
ui.add_space(12.0);
|
ui.add_space(12.0);
|
||||||
|
|
||||||
let last_contact_list_size = GLOBALS
|
let latest_event_data = GLOBALS
|
||||||
.people
|
.people
|
||||||
.last_contact_list_size
|
.latest_person_list_event_data
|
||||||
.load(Ordering::Relaxed);
|
.get(&PersonList::Followed)
|
||||||
let last_contact_list_asof = GLOBALS
|
.map(|v| v.value().clone())
|
||||||
.people
|
.unwrap_or(Default::default());
|
||||||
.last_contact_list_asof
|
|
||||||
.load(Ordering::Relaxed);
|
|
||||||
let mut asof = "unknown".to_owned();
|
let mut asof = "unknown".to_owned();
|
||||||
if let Ok(stamp) = time::OffsetDateTime::from_unix_timestamp(last_contact_list_asof) {
|
if let Ok(stamp) = time::OffsetDateTime::from_unix_timestamp(latest_event_data.when.0) {
|
||||||
if let Ok(formatted) = stamp.format(time::macros::format_description!(
|
if let Ok(formatted) = stamp.format(time::macros::format_description!(
|
||||||
"[year]-[month repr:short]-[day] ([weekday repr:short]) [hour]:[minute]"
|
"[year]-[month repr:short]-[day] ([weekday repr:short]) [hour]:[minute]"
|
||||||
)) {
|
)) {
|
||||||
@ -42,8 +43,8 @@ pub(super) fn update(app: &mut GossipUi, ctx: &Context, _frame: &mut eframe::Fra
|
|||||||
|
|
||||||
ui.label(
|
ui.label(
|
||||||
RichText::new(format!(
|
RichText::new(format!(
|
||||||
"REMOTE: {} (size={})",
|
"REMOTE: {} (len={})",
|
||||||
asof, last_contact_list_size
|
asof, latest_event_data.public_len
|
||||||
))
|
))
|
||||||
.size(15.0),
|
.size(15.0),
|
||||||
)
|
)
|
||||||
@ -63,7 +64,10 @@ pub(super) fn update(app: &mut GossipUi, ctx: &Context, _frame: &mut eframe::Fra
|
|||||||
{
|
{
|
||||||
let _ = GLOBALS
|
let _ = GLOBALS
|
||||||
.to_overlord
|
.to_overlord
|
||||||
.send(ToOverlordMessage::UpdateFollowing { merge: false });
|
.send(ToOverlordMessage::UpdatePersonList {
|
||||||
|
person_list: PersonList::Followed,
|
||||||
|
merge: false,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if ui
|
if ui
|
||||||
.button("↓ Merge ↓")
|
.button("↓ Merge ↓")
|
||||||
@ -74,7 +78,10 @@ pub(super) fn update(app: &mut GossipUi, ctx: &Context, _frame: &mut eframe::Fra
|
|||||||
{
|
{
|
||||||
let _ = GLOBALS
|
let _ = GLOBALS
|
||||||
.to_overlord
|
.to_overlord
|
||||||
.send(ToOverlordMessage::UpdateFollowing { merge: true });
|
.send(ToOverlordMessage::UpdatePersonList {
|
||||||
|
person_list: PersonList::Followed,
|
||||||
|
merge: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if GLOBALS.signer.is_ready() {
|
if GLOBALS.signer.is_ready() {
|
||||||
@ -83,7 +90,9 @@ pub(super) fn update(app: &mut GossipUi, ctx: &Context, _frame: &mut eframe::Fra
|
|||||||
.on_hover_text("This publishes your Contact List")
|
.on_hover_text("This publishes your Contact List")
|
||||||
.clicked()
|
.clicked()
|
||||||
{
|
{
|
||||||
let _ = GLOBALS.to_overlord.send(ToOverlordMessage::PushFollow);
|
let _ = GLOBALS
|
||||||
|
.to_overlord
|
||||||
|
.send(ToOverlordMessage::PushPersonList(PersonList::Followed));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,8 +127,12 @@ pub(super) fn update(app: &mut GossipUi, ctx: &Context, _frame: &mut eframe::Fra
|
|||||||
|
|
||||||
ui.add_space(10.0);
|
ui.add_space(10.0);
|
||||||
|
|
||||||
let last_contact_list_edit = match GLOBALS.storage.read_last_contact_list_edit() {
|
let last_contact_list_edit = match GLOBALS
|
||||||
Ok(date) => date,
|
.storage
|
||||||
|
.get_person_list_last_edit_time(PersonList::Followed)
|
||||||
|
{
|
||||||
|
Ok(Some(date)) => date,
|
||||||
|
Ok(None) => 0,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::error!("{}", e);
|
tracing::error!("{}", e);
|
||||||
0
|
0
|
||||||
|
@ -3,12 +3,14 @@ use crate::AVATAR_SIZE_F32;
|
|||||||
use eframe::egui;
|
use eframe::egui;
|
||||||
use egui::{Context, Image, RichText, Sense, Ui, Vec2};
|
use egui::{Context, Image, RichText, Sense, Ui, Vec2};
|
||||||
use gossip_lib::comms::ToOverlordMessage;
|
use gossip_lib::comms::ToOverlordMessage;
|
||||||
use gossip_lib::Person;
|
use gossip_lib::{Person, PersonList, GLOBALS};
|
||||||
use gossip_lib::GLOBALS;
|
|
||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
|
|
||||||
pub(super) fn update(app: &mut GossipUi, ctx: &Context, _frame: &mut eframe::Frame, ui: &mut Ui) {
|
pub(super) fn update(app: &mut GossipUi, ctx: &Context, _frame: &mut eframe::Frame, ui: &mut Ui) {
|
||||||
let muted_pubkeys = GLOBALS.people.get_muted_pubkeys();
|
let muted_pubkeys = GLOBALS
|
||||||
|
.storage
|
||||||
|
.get_people_in_list(PersonList::Muted, None)
|
||||||
|
.unwrap_or(vec![]);
|
||||||
|
|
||||||
let mut people: Vec<Person> = Vec::new();
|
let mut people: Vec<Person> = Vec::new();
|
||||||
for pk in &muted_pubkeys {
|
for pk in &muted_pubkeys {
|
||||||
@ -20,14 +22,19 @@ pub(super) fn update(app: &mut GossipUi, ctx: &Context, _frame: &mut eframe::Fra
|
|||||||
people.push(person);
|
people.push(person);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
people.sort_unstable();
|
people.sort();
|
||||||
|
|
||||||
ui.add_space(12.0);
|
ui.add_space(12.0);
|
||||||
|
|
||||||
let last_mute_list_size = GLOBALS.people.last_mute_list_size.load(Ordering::Relaxed);
|
let latest_event_data = GLOBALS
|
||||||
let last_mute_list_asof = GLOBALS.people.last_mute_list_asof.load(Ordering::Relaxed);
|
.people
|
||||||
|
.latest_person_list_event_data
|
||||||
|
.get(&PersonList::Muted)
|
||||||
|
.map(|v| v.value().clone())
|
||||||
|
.unwrap_or(Default::default());
|
||||||
|
|
||||||
let mut asof = "unknown".to_owned();
|
let mut asof = "unknown".to_owned();
|
||||||
if let Ok(stamp) = time::OffsetDateTime::from_unix_timestamp(last_mute_list_asof) {
|
if let Ok(stamp) = time::OffsetDateTime::from_unix_timestamp(latest_event_data.when.0) {
|
||||||
if let Ok(formatted) = stamp.format(time::macros::format_description!(
|
if let Ok(formatted) = stamp.format(time::macros::format_description!(
|
||||||
"[year]-[month repr:short]-[day] ([weekday repr:short]) [hour]:[minute]"
|
"[year]-[month repr:short]-[day] ([weekday repr:short]) [hour]:[minute]"
|
||||||
)) {
|
)) {
|
||||||
@ -35,7 +42,18 @@ pub(super) fn update(app: &mut GossipUi, ctx: &Context, _frame: &mut eframe::Fra
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.label(RichText::new(format!("REMOTE: {} (size={})", asof, last_mute_list_size)).size(15.0))
|
let txt = if let Some(private_len) = latest_event_data.private_len {
|
||||||
|
format!(
|
||||||
|
"REMOTE: {} (public_len={} private_len={})",
|
||||||
|
asof, latest_event_data.public_len, private_len
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
format!(
|
||||||
|
"REMOTE: {} (public_len={})",
|
||||||
|
asof, latest_event_data.public_len
|
||||||
|
)
|
||||||
|
};
|
||||||
|
ui.label(RichText::new(txt).size(15.0))
|
||||||
.on_hover_text("This is the data in the latest MuteList event fetched from relays");
|
.on_hover_text("This is the data in the latest MuteList event fetched from relays");
|
||||||
|
|
||||||
ui.add_space(10.0);
|
ui.add_space(10.0);
|
||||||
@ -50,7 +68,10 @@ pub(super) fn update(app: &mut GossipUi, ctx: &Context, _frame: &mut eframe::Fra
|
|||||||
{
|
{
|
||||||
let _ = GLOBALS
|
let _ = GLOBALS
|
||||||
.to_overlord
|
.to_overlord
|
||||||
.send(ToOverlordMessage::UpdateMuteList { merge: false });
|
.send(ToOverlordMessage::UpdatePersonList {
|
||||||
|
person_list: PersonList::Muted,
|
||||||
|
merge: false,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if ui
|
if ui
|
||||||
.button("↓ Merge ↓")
|
.button("↓ Merge ↓")
|
||||||
@ -59,7 +80,10 @@ pub(super) fn update(app: &mut GossipUi, ctx: &Context, _frame: &mut eframe::Fra
|
|||||||
{
|
{
|
||||||
let _ = GLOBALS
|
let _ = GLOBALS
|
||||||
.to_overlord
|
.to_overlord
|
||||||
.send(ToOverlordMessage::UpdateMuteList { merge: true });
|
.send(ToOverlordMessage::UpdatePersonList {
|
||||||
|
person_list: PersonList::Muted,
|
||||||
|
merge: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if GLOBALS.signer.is_ready() {
|
if GLOBALS.signer.is_ready() {
|
||||||
@ -68,7 +92,9 @@ pub(super) fn update(app: &mut GossipUi, ctx: &Context, _frame: &mut eframe::Fra
|
|||||||
.on_hover_text("This publishes your Mute List")
|
.on_hover_text("This publishes your Mute List")
|
||||||
.clicked()
|
.clicked()
|
||||||
{
|
{
|
||||||
let _ = GLOBALS.to_overlord.send(ToOverlordMessage::PushMuteList);
|
let _ = GLOBALS
|
||||||
|
.to_overlord
|
||||||
|
.send(ToOverlordMessage::PushPersonList(PersonList::Muted));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,8 +117,12 @@ pub(super) fn update(app: &mut GossipUi, ctx: &Context, _frame: &mut eframe::Fra
|
|||||||
|
|
||||||
ui.add_space(10.0);
|
ui.add_space(10.0);
|
||||||
|
|
||||||
let last_mute_list_edit = match GLOBALS.storage.read_last_mute_list_edit() {
|
let last_mute_list_edit = match GLOBALS
|
||||||
Ok(date) => date,
|
.storage
|
||||||
|
.get_person_list_last_edit_time(PersonList::Muted)
|
||||||
|
{
|
||||||
|
Ok(Some(date)) => date,
|
||||||
|
Ok(None) => 0,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::error!("{}", e);
|
tracing::error!("{}", e);
|
||||||
0
|
0
|
||||||
|
@ -17,7 +17,10 @@ pub(super) fn update(app: &mut GossipUi, _ctx: &Context, _frame: &mut eframe::Fr
|
|||||||
if app.wizard_state.contacts_sought {
|
if app.wizard_state.contacts_sought {
|
||||||
let _ = GLOBALS
|
let _ = GLOBALS
|
||||||
.to_overlord
|
.to_overlord
|
||||||
.send(ToOverlordMessage::UpdateFollowing { merge: false });
|
.send(ToOverlordMessage::UpdatePersonList {
|
||||||
|
person_list: PersonList::Followed,
|
||||||
|
merge: false,
|
||||||
|
});
|
||||||
app.wizard_state.contacts_sought = false;
|
app.wizard_state.contacts_sought = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,7 +142,9 @@ pub(super) fn update(app: &mut GossipUi, _ctx: &Context, _frame: &mut eframe::Fr
|
|||||||
label = label.color(app.theme.accent_color());
|
label = label.color(app.theme.accent_color());
|
||||||
}
|
}
|
||||||
if ui.button(label).clicked() {
|
if ui.button(label).clicked() {
|
||||||
let _ = GLOBALS.to_overlord.send(ToOverlordMessage::PushFollow);
|
let _ = GLOBALS
|
||||||
|
.to_overlord
|
||||||
|
.send(ToOverlordMessage::PushPersonList(PersonList::Followed));
|
||||||
|
|
||||||
let _ = GLOBALS.storage.write_wizard_complete(true, None);
|
let _ = GLOBALS.storage.write_wizard_complete(true, None);
|
||||||
app.page = Page::Feed(FeedKind::List(PersonList::Followed, false));
|
app.page = Page::Feed(FeedKind::List(PersonList::Followed, false));
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
use gossip_lib::Relay;
|
use gossip_lib::{PersonList, Relay, GLOBALS};
|
||||||
use gossip_lib::GLOBALS;
|
|
||||||
use nostr_types::{Event, EventKind, PublicKey, RelayUrl};
|
use nostr_types::{Event, EventKind, PublicKey, RelayUrl};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
@ -80,7 +79,10 @@ impl WizardState {
|
|||||||
.unwrap_or(Vec::new());
|
.unwrap_or(Vec::new());
|
||||||
}
|
}
|
||||||
|
|
||||||
self.followed = GLOBALS.people.get_followed_pubkeys();
|
self.followed = GLOBALS
|
||||||
|
.storage
|
||||||
|
.get_people_in_list(PersonList::Followed, None)
|
||||||
|
.unwrap_or(vec![]);
|
||||||
|
|
||||||
if self.need_discovery_relays() {
|
if self.need_discovery_relays() {
|
||||||
let purplepages = RelayUrl::try_from_str("wss://purplepag.es/").unwrap();
|
let purplepages = RelayUrl::try_from_str("wss://purplepag.es/").unwrap();
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use crate::dm_channel::DmChannel;
|
use crate::dm_channel::DmChannel;
|
||||||
|
use crate::people::PersonList;
|
||||||
use nostr_types::{
|
use nostr_types::{
|
||||||
Event, EventAddr, Id, IdHex, Metadata, MilliSatoshi, Profile, PublicKey, RelayUrl, Tag,
|
Event, EventAddr, Id, IdHex, Metadata, MilliSatoshi, Profile, PublicKey, RelayUrl, Tag,
|
||||||
UncheckedUrl,
|
UncheckedUrl,
|
||||||
@ -102,15 +103,12 @@ pub enum ToOverlordMessage {
|
|||||||
/// Calls [prune_database](crate::Overlord::prune_database)
|
/// Calls [prune_database](crate::Overlord::prune_database)
|
||||||
PruneDatabase,
|
PruneDatabase,
|
||||||
|
|
||||||
/// Calls [push_follow](crate::Overlord::push_follow)
|
/// Calls [push_person_list](crate::Overlord::push_person_list)
|
||||||
PushFollow,
|
PushPersonList(PersonList),
|
||||||
|
|
||||||
/// Calls [push_metadata](crate::Overlord::push_metadata)
|
/// Calls [push_metadata](crate::Overlord::push_metadata)
|
||||||
PushMetadata(Metadata),
|
PushMetadata(Metadata),
|
||||||
|
|
||||||
/// Calls [push_mute_list](crate::Overlord::push_mute_list)
|
|
||||||
PushMuteList,
|
|
||||||
|
|
||||||
/// Calls [rank_relay](crate::Overlord::rank_relay)
|
/// Calls [rank_relay](crate::Overlord::rank_relay)
|
||||||
RankRelay(RelayUrl, u8),
|
RankRelay(RelayUrl, u8),
|
||||||
|
|
||||||
@ -152,17 +150,17 @@ pub enum ToOverlordMessage {
|
|||||||
/// Calls [unlock_key](crate::Overlord::unlock_key)
|
/// Calls [unlock_key](crate::Overlord::unlock_key)
|
||||||
UnlockKey(String),
|
UnlockKey(String),
|
||||||
|
|
||||||
/// Calls [update_following](crate::Overlord::update_following)
|
|
||||||
UpdateFollowing { merge: bool },
|
|
||||||
|
|
||||||
/// Calls [update_metadata](crate::Overlord::update_metadata)
|
/// Calls [update_metadata](crate::Overlord::update_metadata)
|
||||||
UpdateMetadata(PublicKey),
|
UpdateMetadata(PublicKey),
|
||||||
|
|
||||||
/// Calls [update_metadata_in_bulk](crate::Overlord::update_metadata_in_bulk)
|
/// Calls [update_metadata_in_bulk](crate::Overlord::update_metadata_in_bulk)
|
||||||
UpdateMetadataInBulk(Vec<PublicKey>),
|
UpdateMetadataInBulk(Vec<PublicKey>),
|
||||||
|
|
||||||
/// Calls [update_mute_list](crate::Overlord::update_mute_list)
|
/// Calls [update_person_list](crate::Overlord::update_person_list)
|
||||||
UpdateMuteList { merge: bool },
|
UpdatePersonList {
|
||||||
|
person_list: PersonList,
|
||||||
|
merge: bool,
|
||||||
|
},
|
||||||
|
|
||||||
/// Calls [visible_notes_changed](crate::Overlord::visible_notes_changed)
|
/// Calls [visible_notes_changed](crate::Overlord::visible_notes_changed)
|
||||||
VisibleNotesChanged(Vec<Id>),
|
VisibleNotesChanged(Vec<Id>),
|
||||||
|
@ -17,6 +17,7 @@ pub enum ErrorKind {
|
|||||||
MpscSend(tokio::sync::mpsc::error::SendError<ToOverlordMessage>),
|
MpscSend(tokio::sync::mpsc::error::SendError<ToOverlordMessage>),
|
||||||
Nip05KeyNotFound,
|
Nip05KeyNotFound,
|
||||||
Nostr(nostr_types::Error),
|
Nostr(nostr_types::Error),
|
||||||
|
NoPublicKey,
|
||||||
NoPrivateKey,
|
NoPrivateKey,
|
||||||
NoRelay,
|
NoRelay,
|
||||||
NoSlotsRemaining,
|
NoSlotsRemaining,
|
||||||
@ -88,6 +89,7 @@ impl std::fmt::Display for Error {
|
|||||||
MpscSend(e) => write!(f, "Error sending mpsc: {e}"),
|
MpscSend(e) => write!(f, "Error sending mpsc: {e}"),
|
||||||
Nip05KeyNotFound => write!(f, "NIP-05 public key not found"),
|
Nip05KeyNotFound => write!(f, "NIP-05 public key not found"),
|
||||||
Nostr(e) => write!(f, "Nostr: {e}"),
|
Nostr(e) => write!(f, "Nostr: {e}"),
|
||||||
|
NoPublicKey => write!(f, "No public key identity available."),
|
||||||
NoPrivateKey => write!(f, "No private key available."),
|
NoPrivateKey => write!(f, "No private key available."),
|
||||||
NoRelay => write!(f, "Could not determine a relay to use."),
|
NoRelay => write!(f, "Could not determine a relay to use."),
|
||||||
NoSlotsRemaining => write!(f, "No custom list slots remaining."),
|
NoSlotsRemaining => write!(f, "No custom list slots remaining."),
|
||||||
|
@ -486,6 +486,10 @@ pub fn enabled_event_kinds() -> Vec<EventKind> {
|
|||||||
&& ((*k != EventKind::DmChat) || direct_messages)
|
&& ((*k != EventKind::DmChat) || direct_messages)
|
||||||
&& ((*k != EventKind::GiftWrap) || direct_messages)
|
&& ((*k != EventKind::GiftWrap) || direct_messages)
|
||||||
&& ((*k != EventKind::Zap) || enable_zap_receipts)
|
&& ((*k != EventKind::Zap) || enable_zap_receipts)
|
||||||
|
&& (*k != EventKind::ChannelMessage) // not yet implemented
|
||||||
|
&& (*k != EventKind::LiveChatMessage) // not yet implemented
|
||||||
|
&& (*k != EventKind::CommunityPost) // not yet implemented
|
||||||
|
&& (*k != EventKind::DraftLongFormContent) // not yet implemented
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ use crate::comms::{
|
|||||||
use crate::dm_channel::DmChannel;
|
use crate::dm_channel::DmChannel;
|
||||||
use crate::error::{Error, ErrorKind};
|
use crate::error::{Error, ErrorKind};
|
||||||
use crate::globals::{ZapState, GLOBALS};
|
use crate::globals::{ZapState, GLOBALS};
|
||||||
use crate::people::Person;
|
use crate::people::{Person, PersonList};
|
||||||
use crate::person_relay::PersonRelay;
|
use crate::person_relay::PersonRelay;
|
||||||
use crate::relay::Relay;
|
use crate::relay::Relay;
|
||||||
use crate::tags::{
|
use crate::tags::{
|
||||||
@ -15,6 +15,7 @@ use crate::tags::{
|
|||||||
add_subject_to_tags_if_missing,
|
add_subject_to_tags_if_missing,
|
||||||
};
|
};
|
||||||
use gossip_relay_picker::{Direction, RelayAssignment};
|
use gossip_relay_picker::{Direction, RelayAssignment};
|
||||||
|
use heed::RwTxn;
|
||||||
use http::StatusCode;
|
use http::StatusCode;
|
||||||
use minion::Minion;
|
use minion::Minion;
|
||||||
use nostr_types::{
|
use nostr_types::{
|
||||||
@ -639,15 +640,12 @@ impl Overlord {
|
|||||||
ToOverlordMessage::PruneDatabase => {
|
ToOverlordMessage::PruneDatabase => {
|
||||||
Self::prune_database()?;
|
Self::prune_database()?;
|
||||||
}
|
}
|
||||||
ToOverlordMessage::PushFollow => {
|
ToOverlordMessage::PushPersonList(person_list) => {
|
||||||
self.push_follow().await?;
|
self.push_person_list(person_list).await?;
|
||||||
}
|
}
|
||||||
ToOverlordMessage::PushMetadata(metadata) => {
|
ToOverlordMessage::PushMetadata(metadata) => {
|
||||||
self.push_metadata(metadata).await?;
|
self.push_metadata(metadata).await?;
|
||||||
}
|
}
|
||||||
ToOverlordMessage::PushMuteList => {
|
|
||||||
self.push_mute_list().await?;
|
|
||||||
}
|
|
||||||
ToOverlordMessage::RankRelay(relay_url, rank) => {
|
ToOverlordMessage::RankRelay(relay_url, rank) => {
|
||||||
Self::rank_relay(relay_url, rank)?;
|
Self::rank_relay(relay_url, rank)?;
|
||||||
}
|
}
|
||||||
@ -690,17 +688,14 @@ impl Overlord {
|
|||||||
ToOverlordMessage::UnlockKey(password) => {
|
ToOverlordMessage::UnlockKey(password) => {
|
||||||
Self::unlock_key(password)?;
|
Self::unlock_key(password)?;
|
||||||
}
|
}
|
||||||
ToOverlordMessage::UpdateFollowing { merge } => {
|
|
||||||
self.update_following(merge).await?;
|
|
||||||
}
|
|
||||||
ToOverlordMessage::UpdateMetadata(pubkey) => {
|
ToOverlordMessage::UpdateMetadata(pubkey) => {
|
||||||
self.update_metadata(pubkey).await?;
|
self.update_metadata(pubkey).await?;
|
||||||
}
|
}
|
||||||
ToOverlordMessage::UpdateMetadataInBulk(pubkeys) => {
|
ToOverlordMessage::UpdateMetadataInBulk(pubkeys) => {
|
||||||
self.update_metadata_in_bulk(pubkeys).await?;
|
self.update_metadata_in_bulk(pubkeys).await?;
|
||||||
}
|
}
|
||||||
ToOverlordMessage::UpdateMuteList { merge } => {
|
ToOverlordMessage::UpdatePersonList { person_list, merge } => {
|
||||||
self.update_mute_list(merge).await?;
|
self.update_person_list(person_list, merge).await?;
|
||||||
}
|
}
|
||||||
ToOverlordMessage::VisibleNotesChanged(visible) => {
|
ToOverlordMessage::VisibleNotesChanged(visible) => {
|
||||||
self.visible_notes_changed(visible).await?;
|
self.visible_notes_changed(visible).await?;
|
||||||
@ -1599,9 +1594,9 @@ impl Overlord {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Publish the user's following list
|
/// Publish the user's specified PersonList
|
||||||
pub async fn push_follow(&mut self) -> Result<(), Error> {
|
pub async fn push_person_list(&mut self, list: PersonList) -> Result<(), Error> {
|
||||||
let event = GLOBALS.people.generate_contact_list_event().await?;
|
let event = GLOBALS.people.generate_person_list_event(list).await?;
|
||||||
|
|
||||||
// process event locally
|
// process event locally
|
||||||
crate::process::process_new_event(&event, None, None, false, false).await?;
|
crate::process::process_new_event(&event, None, None, false, false).await?;
|
||||||
@ -1613,7 +1608,7 @@ impl Overlord {
|
|||||||
|
|
||||||
for relay in relays {
|
for relay in relays {
|
||||||
// Send it the event to pull our followers
|
// Send it the event to pull our followers
|
||||||
tracing::debug!("Pushing ContactList to {}", &relay.url);
|
tracing::debug!("Pushing PersonList={} to {}", list.name(), &relay.url);
|
||||||
|
|
||||||
self.engage_minion(
|
self.engage_minion(
|
||||||
relay.url.clone(),
|
relay.url.clone(),
|
||||||
@ -1673,38 +1668,6 @@ impl Overlord {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Publish the user's mute list
|
|
||||||
pub async fn push_mute_list(&mut self) -> Result<(), Error> {
|
|
||||||
let event = GLOBALS.people.generate_mute_list_event().await?;
|
|
||||||
|
|
||||||
// process event locally
|
|
||||||
crate::process::process_new_event(&event, None, None, false, false).await?;
|
|
||||||
|
|
||||||
// Push to all of the relays we post to
|
|
||||||
let relays: Vec<Relay> = GLOBALS
|
|
||||||
.storage
|
|
||||||
.filter_relays(|r| r.has_usage_bits(Relay::WRITE) && r.rank != 0)?;
|
|
||||||
|
|
||||||
for relay in relays {
|
|
||||||
// Send it the event to pull our followers
|
|
||||||
tracing::debug!("Pushing MuteList to {}", &relay.url);
|
|
||||||
|
|
||||||
self.engage_minion(
|
|
||||||
relay.url.clone(),
|
|
||||||
vec![RelayJob {
|
|
||||||
reason: RelayConnectionReason::PostMuteList,
|
|
||||||
payload: ToMinionPayload {
|
|
||||||
job_id: rand::random::<u64>(),
|
|
||||||
detail: ToMinionPayloadDetail::PostEvent(Box::new(event.clone())),
|
|
||||||
},
|
|
||||||
}],
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Rank a relay from 0 to 9. The default rank is 3. A rank of 0 means the relay will not be used.
|
/// Rank a relay from 0 to 9. The default rank is 3. A rank of 0 means the relay will not be used.
|
||||||
/// This represent a user's judgement, and is factored into how suitable a relay is for various
|
/// This represent a user's judgement, and is factored into how suitable a relay is for various
|
||||||
/// purposes.
|
/// purposes.
|
||||||
@ -2245,127 +2208,6 @@ impl Overlord {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the local following list from the last ContactList event received.
|
|
||||||
pub async fn update_following(&mut self, merge: bool) -> Result<(), Error> {
|
|
||||||
// Load the latest contact list from the database
|
|
||||||
let our_contact_list = {
|
|
||||||
let pubkey = match GLOBALS.signer.public_key() {
|
|
||||||
Some(pk) => pk,
|
|
||||||
None => return Ok(()), // we cannot do anything without an identity setup first
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(event) = GLOBALS
|
|
||||||
.storage
|
|
||||||
.get_replaceable_event(pubkey, EventKind::ContactList)?
|
|
||||||
{
|
|
||||||
event.clone()
|
|
||||||
} else {
|
|
||||||
return Ok(()); // we have no contact list to update from
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut pubkeys: Vec<PublicKey> = Vec::new();
|
|
||||||
|
|
||||||
let now = Unixtime::now().unwrap();
|
|
||||||
|
|
||||||
let mut txn = GLOBALS.storage.get_write_txn()?;
|
|
||||||
|
|
||||||
// 'p' tags represent the author's contacts
|
|
||||||
for tag in &our_contact_list.tags {
|
|
||||||
if let Tag::Pubkey {
|
|
||||||
pubkey,
|
|
||||||
recommended_relay_url,
|
|
||||||
petname,
|
|
||||||
..
|
|
||||||
} = tag
|
|
||||||
{
|
|
||||||
if let Ok(pubkey) = PublicKey::try_from_hex_string(pubkey, true) {
|
|
||||||
// Save the pubkey for actual following them (outside of the loop in a batch)
|
|
||||||
pubkeys.push(pubkey.to_owned());
|
|
||||||
|
|
||||||
// If there is a URL
|
|
||||||
if let Some(url) = recommended_relay_url
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|rru| RelayUrl::try_from_unchecked_url(rru).ok())
|
|
||||||
{
|
|
||||||
// Save relay if missing
|
|
||||||
GLOBALS
|
|
||||||
.storage
|
|
||||||
.write_relay_if_missing(&url, Some(&mut txn))?;
|
|
||||||
|
|
||||||
// create or update person_relay last_suggested_kind3
|
|
||||||
let mut pr = match GLOBALS.storage.read_person_relay(pubkey, &url)? {
|
|
||||||
Some(pr) => pr,
|
|
||||||
None => PersonRelay::new(pubkey, url.clone()),
|
|
||||||
};
|
|
||||||
pr.last_suggested_kind3 = Some(now.0 as u64);
|
|
||||||
GLOBALS.storage.write_person_relay(&pr, Some(&mut txn))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle petname
|
|
||||||
if merge && petname.is_none() {
|
|
||||||
// In this case, we leave any existing petname, so no need to load the
|
|
||||||
// person record. But we need to ensure the person exists
|
|
||||||
GLOBALS
|
|
||||||
.storage
|
|
||||||
.write_person_if_missing(&pubkey, Some(&mut txn))?;
|
|
||||||
} else {
|
|
||||||
// In every other case we have to load the person and compare
|
|
||||||
let mut person_needs_save = false;
|
|
||||||
let mut person = match GLOBALS.storage.read_person(&pubkey)? {
|
|
||||||
Some(person) => person,
|
|
||||||
None => {
|
|
||||||
person_needs_save = true;
|
|
||||||
Person::new(pubkey.to_owned())
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if *petname != person.petname {
|
|
||||||
if petname.is_some() {
|
|
||||||
person_needs_save = true;
|
|
||||||
person.petname = petname.clone();
|
|
||||||
} else if !merge {
|
|
||||||
// In overwrite mode, clear to None
|
|
||||||
person_needs_save = true;
|
|
||||||
person.petname = None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if person_needs_save {
|
|
||||||
GLOBALS.storage.write_person(&person, Some(&mut txn))?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
txn.commit()?;
|
|
||||||
|
|
||||||
// Follow all those pubkeys publicly, and unfollow everbody else if merge=false
|
|
||||||
GLOBALS.people.follow_all(&pubkeys, merge, true)?;
|
|
||||||
|
|
||||||
// Update last_contact_list_edit
|
|
||||||
let last_edit = if merge {
|
|
||||||
Unixtime::now().unwrap() // now, since superior to the last event
|
|
||||||
} else {
|
|
||||||
our_contact_list.created_at
|
|
||||||
};
|
|
||||||
GLOBALS
|
|
||||||
.storage
|
|
||||||
.write_last_contact_list_edit(last_edit.0, None)?;
|
|
||||||
|
|
||||||
// Pick relays again
|
|
||||||
{
|
|
||||||
// Refresh person-relay scores
|
|
||||||
GLOBALS.relay_picker.refresh_person_relay_scores().await?;
|
|
||||||
|
|
||||||
// Then pick
|
|
||||||
self.pick_relays().await;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Subscribe, fetch, and update metadata for the person
|
/// Subscribe, fetch, and update metadata for the person
|
||||||
pub async fn update_metadata(&mut self, pubkey: PublicKey) -> Result<(), Error> {
|
pub async fn update_metadata(&mut self, pubkey: PublicKey) -> Result<(), Error> {
|
||||||
let best_relays = GLOBALS.storage.get_best_relays(pubkey, Direction::Write)?;
|
let best_relays = GLOBALS.storage.get_best_relays(pubkey, Direction::Write)?;
|
||||||
@ -2427,49 +2269,163 @@ impl Overlord {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the local mute list from the last ContactList event received.
|
/// Update the local mute list from the last MuteList event received.
|
||||||
pub async fn update_mute_list(&mut self, merge: bool) -> Result<(), Error> {
|
pub async fn update_person_list(&mut self, list: PersonList, merge: bool) -> Result<(), Error> {
|
||||||
// Load the latest MuteList from the database
|
// we cannot do anything without an identity setup first
|
||||||
let our_mute_list = {
|
let my_pubkey = match GLOBALS.storage.read_setting_public_key() {
|
||||||
let pubkey = match GLOBALS.signer.public_key() {
|
Some(pk) => pk,
|
||||||
Some(pk) => pk,
|
None => return Err(ErrorKind::NoPublicKey.into()),
|
||||||
None => return Ok(()), // we cannot do anything without an identity setup first
|
};
|
||||||
};
|
|
||||||
|
|
||||||
|
// Load the latest PersonList event from the database
|
||||||
|
let event = {
|
||||||
if let Some(event) = GLOBALS
|
if let Some(event) = GLOBALS
|
||||||
.storage
|
.storage
|
||||||
.get_replaceable_event(pubkey, EventKind::MuteList)?
|
.get_replaceable_event(my_pubkey, list.event_kind())?
|
||||||
{
|
{
|
||||||
event.clone()
|
event.clone()
|
||||||
} else {
|
} else {
|
||||||
return Ok(()); // we have no mute list to update from
|
return Ok(()); // we have no event to update from, so we are done
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut pubkeys: Vec<PublicKey> = Vec::new();
|
let now = Unixtime::now().unwrap();
|
||||||
|
|
||||||
// 'p' tags represent the author's mutes
|
let mut txn = GLOBALS.storage.get_write_txn()?;
|
||||||
for tag in &our_mute_list.tags {
|
|
||||||
if let Tag::Pubkey { pubkey, .. } = tag {
|
let mut entries: Vec<(PublicKey, bool)> = Vec::new();
|
||||||
|
|
||||||
|
// Public entries
|
||||||
|
for tag in &event.tags {
|
||||||
|
if let Tag::Pubkey {
|
||||||
|
pubkey,
|
||||||
|
recommended_relay_url,
|
||||||
|
petname,
|
||||||
|
..
|
||||||
|
} = tag
|
||||||
|
{
|
||||||
if let Ok(pubkey) = PublicKey::try_from_hex_string(pubkey, true) {
|
if let Ok(pubkey) = PublicKey::try_from_hex_string(pubkey, true) {
|
||||||
// Save the pubkey
|
// Save the pubkey
|
||||||
pubkeys.push(pubkey.to_owned());
|
entries.push((pubkey.to_owned(), true));
|
||||||
|
|
||||||
|
// Deal with recommended_relay_urls and petnames
|
||||||
|
if list == PersonList::Followed {
|
||||||
|
Self::integrate_rru_and_petname(
|
||||||
|
&pubkey,
|
||||||
|
recommended_relay_url,
|
||||||
|
petname,
|
||||||
|
now,
|
||||||
|
merge,
|
||||||
|
&mut txn,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mute all those pubkeys publicly, and unmute everbody else if merge=false
|
// Private entries
|
||||||
GLOBALS.people.mute_all(&pubkeys, merge, true)?;
|
if list != PersonList::Followed {
|
||||||
|
let decrypted_content = GLOBALS.signer.decrypt_nip04(&my_pubkey, &event.content)?;
|
||||||
|
|
||||||
|
let tags: Vec<Tag> = serde_json::from_slice(&decrypted_content)?;
|
||||||
|
|
||||||
|
for tag in &tags {
|
||||||
|
if let Tag::Pubkey { pubkey, .. } = tag {
|
||||||
|
if let Ok(pubkey) = PublicKey::try_from_hex_string(pubkey, true) {
|
||||||
|
// Save the pubkey
|
||||||
|
entries.push((pubkey.to_owned(), false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !merge {
|
||||||
|
GLOBALS.storage.clear_person_list(list, Some(&mut txn))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (pubkey, public) in &entries {
|
||||||
|
GLOBALS
|
||||||
|
.storage
|
||||||
|
.add_person_to_list(pubkey, list, *public, Some(&mut txn))?;
|
||||||
|
GLOBALS.ui_people_to_invalidate.write().push(*pubkey);
|
||||||
|
}
|
||||||
|
|
||||||
|
let last_edit = if merge { now } else { event.created_at };
|
||||||
|
|
||||||
// Update last_must_list_edit
|
|
||||||
let last_edit = if merge {
|
|
||||||
Unixtime::now().unwrap() // now, since superior to the last event
|
|
||||||
} else {
|
|
||||||
our_mute_list.created_at
|
|
||||||
};
|
|
||||||
GLOBALS
|
GLOBALS
|
||||||
.storage
|
.storage
|
||||||
.write_last_mute_list_edit(last_edit.0, None)?;
|
.set_person_list_last_edit_time(list, last_edit.0, Some(&mut txn))?;
|
||||||
|
|
||||||
|
txn.commit()?;
|
||||||
|
|
||||||
|
// Pick relays again
|
||||||
|
if list.subscribe() {
|
||||||
|
// Refresh person-relay scores
|
||||||
|
GLOBALS.relay_picker.refresh_person_relay_scores().await?;
|
||||||
|
|
||||||
|
// Then pick
|
||||||
|
self.pick_relays().await;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn integrate_rru_and_petname(
|
||||||
|
pubkey: &PublicKey,
|
||||||
|
recommended_relay_url: &Option<UncheckedUrl>,
|
||||||
|
petname: &Option<String>,
|
||||||
|
now: Unixtime,
|
||||||
|
merge: bool,
|
||||||
|
txn: &mut RwTxn,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
// If there is a URL
|
||||||
|
if let Some(url) = recommended_relay_url
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|rru| RelayUrl::try_from_unchecked_url(rru).ok())
|
||||||
|
{
|
||||||
|
// Save relay if missing
|
||||||
|
GLOBALS.storage.write_relay_if_missing(&url, Some(txn))?;
|
||||||
|
|
||||||
|
// create or update person_relay last_suggested_kind3
|
||||||
|
let mut pr = match GLOBALS.storage.read_person_relay(*pubkey, &url)? {
|
||||||
|
Some(pr) => pr,
|
||||||
|
None => PersonRelay::new(*pubkey, url.clone()),
|
||||||
|
};
|
||||||
|
pr.last_suggested_kind3 = Some(now.0 as u64);
|
||||||
|
GLOBALS.storage.write_person_relay(&pr, Some(txn))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle petname
|
||||||
|
if merge && petname.is_none() {
|
||||||
|
// In this case, we leave any existing petname, so no need to load the
|
||||||
|
// person record. But we need to ensure the person exists
|
||||||
|
GLOBALS.storage.write_person_if_missing(pubkey, Some(txn))?;
|
||||||
|
} else {
|
||||||
|
// In every other case we have to load the person and compare
|
||||||
|
let mut person_needs_save = false;
|
||||||
|
let mut person = match GLOBALS.storage.read_person(pubkey)? {
|
||||||
|
Some(person) => person,
|
||||||
|
None => {
|
||||||
|
person_needs_save = true;
|
||||||
|
Person::new(pubkey.to_owned())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if *petname != person.petname {
|
||||||
|
if petname.is_some() {
|
||||||
|
person_needs_save = true;
|
||||||
|
person.petname = petname.clone();
|
||||||
|
} else if !merge {
|
||||||
|
// In overwrite mode, clear to None
|
||||||
|
person_needs_save = true;
|
||||||
|
person.petname = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if person_needs_save {
|
||||||
|
GLOBALS.storage.write_person(&person, Some(txn))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -5,10 +5,11 @@ use dashmap::{DashMap, DashSet};
|
|||||||
use gossip_relay_picker::Direction;
|
use gossip_relay_picker::Direction;
|
||||||
use image::RgbaImage;
|
use image::RgbaImage;
|
||||||
use nostr_types::{
|
use nostr_types::{
|
||||||
Event, EventKind, Metadata, PreEvent, PublicKey, RelayUrl, Tag, UncheckedUrl, Unixtime, Url,
|
ContentEncryptionAlgorithm, Event, EventKind, Metadata, PreEvent, PublicKey, RelayUrl, Tag,
|
||||||
|
UncheckedUrl, Unixtime, Url,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::sync::atomic::{AtomicI64, AtomicUsize, Ordering};
|
use std::sync::atomic::Ordering;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
use tokio::task;
|
use tokio::task;
|
||||||
@ -19,6 +20,30 @@ pub type Person = crate::storage::types::Person2;
|
|||||||
/// PersonList type, aliased to the latest version
|
/// PersonList type, aliased to the latest version
|
||||||
pub type PersonList = crate::storage::types::PersonList1;
|
pub type PersonList = crate::storage::types::PersonList1;
|
||||||
|
|
||||||
|
/// Person List Compare Data
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct PersonListEventData {
|
||||||
|
/// The timestamp of the latest event
|
||||||
|
pub when: Unixtime,
|
||||||
|
|
||||||
|
/// The number of public entries in the latest event
|
||||||
|
pub public_len: usize,
|
||||||
|
|
||||||
|
/// The number of private entires in the latest event, or None if it
|
||||||
|
/// couldn't be computed (not logged in, Following event, or none found)
|
||||||
|
pub private_len: Option<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for PersonListEventData {
|
||||||
|
fn default() -> PersonListEventData {
|
||||||
|
PersonListEventData {
|
||||||
|
when: Unixtime(0),
|
||||||
|
public_len: 0,
|
||||||
|
private_len: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Handles people and remembers what needs to be done for each, such as fetching
|
/// Handles people and remembers what needs to be done for each, such as fetching
|
||||||
/// metadata or avatars.
|
/// metadata or avatars.
|
||||||
pub struct People {
|
pub struct People {
|
||||||
@ -45,17 +70,8 @@ pub struct People {
|
|||||||
// per gossip run (this set only grows)
|
// per gossip run (this set only grows)
|
||||||
tried_metadata: DashSet<PublicKey>,
|
tried_metadata: DashSet<PublicKey>,
|
||||||
|
|
||||||
// Date of the last self-owned contact list we have an event for
|
/// Latest person list event data for each PersonList
|
||||||
pub last_contact_list_asof: AtomicI64,
|
pub latest_person_list_event_data: DashMap<PersonList, PersonListEventData>,
|
||||||
|
|
||||||
// Size of the last self-owned contact list we have an event for
|
|
||||||
pub last_contact_list_size: AtomicUsize,
|
|
||||||
|
|
||||||
// Date of the last self-owned mute list we have an event for
|
|
||||||
pub last_mute_list_asof: AtomicI64,
|
|
||||||
|
|
||||||
// Size of the last self-owned mute list we have an event for
|
|
||||||
pub last_mute_list_size: AtomicUsize,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for People {
|
impl Default for People {
|
||||||
@ -74,67 +90,13 @@ impl People {
|
|||||||
recheck_nip05: DashSet::new(),
|
recheck_nip05: DashSet::new(),
|
||||||
need_metadata: DashSet::new(),
|
need_metadata: DashSet::new(),
|
||||||
tried_metadata: DashSet::new(),
|
tried_metadata: DashSet::new(),
|
||||||
last_contact_list_asof: AtomicI64::new(0),
|
latest_person_list_event_data: DashMap::new(),
|
||||||
last_contact_list_size: AtomicUsize::new(0),
|
|
||||||
last_mute_list_asof: AtomicI64::new(0),
|
|
||||||
last_mute_list_size: AtomicUsize::new(0),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start the periodic task management
|
// Start the periodic task management
|
||||||
pub(crate) fn start() {
|
pub(crate) fn start() {
|
||||||
if let Some(pk) = GLOBALS.signer.public_key() {
|
GLOBALS.people.update_latest_person_list_event_data();
|
||||||
// Load our contact list from the database in order to populate
|
|
||||||
// last_contact_list_asof and last_contact_list_size
|
|
||||||
if let Ok(Some(event)) = GLOBALS
|
|
||||||
.storage
|
|
||||||
.get_replaceable_event(pk, EventKind::ContactList)
|
|
||||||
{
|
|
||||||
if event.created_at.0
|
|
||||||
> GLOBALS
|
|
||||||
.people
|
|
||||||
.last_contact_list_asof
|
|
||||||
.load(Ordering::Relaxed)
|
|
||||||
{
|
|
||||||
GLOBALS
|
|
||||||
.people
|
|
||||||
.last_contact_list_asof
|
|
||||||
.store(event.created_at.0, Ordering::Relaxed);
|
|
||||||
let size = event
|
|
||||||
.tags
|
|
||||||
.iter()
|
|
||||||
.filter(|t| matches!(t, Tag::Pubkey { .. }))
|
|
||||||
.count();
|
|
||||||
GLOBALS
|
|
||||||
.people
|
|
||||||
.last_contact_list_size
|
|
||||||
.store(size, Ordering::Relaxed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load our mute list from the database in order to populate
|
|
||||||
// last_mute_list_asof and last_mute_list_size
|
|
||||||
if let Ok(Some(event)) = GLOBALS
|
|
||||||
.storage
|
|
||||||
.get_replaceable_event(pk, EventKind::MuteList)
|
|
||||||
{
|
|
||||||
if event.created_at.0 > GLOBALS.people.last_mute_list_asof.load(Ordering::Relaxed) {
|
|
||||||
GLOBALS
|
|
||||||
.people
|
|
||||||
.last_mute_list_asof
|
|
||||||
.store(event.created_at.0, Ordering::Relaxed);
|
|
||||||
let size = event
|
|
||||||
.tags
|
|
||||||
.iter()
|
|
||||||
.filter(|t| matches!(t, Tag::Pubkey { .. }))
|
|
||||||
.count();
|
|
||||||
GLOBALS
|
|
||||||
.people
|
|
||||||
.last_mute_list_size
|
|
||||||
.store(size, Ordering::Relaxed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
task::spawn(async {
|
task::spawn(async {
|
||||||
loop {
|
loop {
|
||||||
@ -155,6 +117,52 @@ impl People {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Search local events for the latest PersonList event for each kind of PersonList,
|
||||||
|
/// and determine their timestamps and lengths, storing result in People.
|
||||||
|
pub fn update_latest_person_list_event_data(&self) {
|
||||||
|
// Get public key, or give up
|
||||||
|
let pk = match GLOBALS.storage.read_setting_public_key() {
|
||||||
|
Some(pk) => pk,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (person_list, _) in PersonList::all_lists() {
|
||||||
|
if let Ok(Some(event)) = GLOBALS
|
||||||
|
.storage
|
||||||
|
.get_replaceable_event(pk, person_list.event_kind())
|
||||||
|
{
|
||||||
|
self.latest_person_list_event_data.insert(
|
||||||
|
person_list,
|
||||||
|
PersonListEventData {
|
||||||
|
when: event.created_at,
|
||||||
|
public_len: event
|
||||||
|
.tags
|
||||||
|
.iter()
|
||||||
|
.filter(|t| matches!(t, Tag::Pubkey { .. }))
|
||||||
|
.count(),
|
||||||
|
private_len: {
|
||||||
|
let mut private_len: Option<usize> = None;
|
||||||
|
if !matches!(person_list, PersonList::Followed)
|
||||||
|
&& GLOBALS.signer.is_ready()
|
||||||
|
{
|
||||||
|
if let Ok(bytes) = GLOBALS.signer.decrypt_nip04(&pk, &event.content)
|
||||||
|
{
|
||||||
|
if let Ok(vectags) = serde_json::from_slice::<Vec<Tag>>(&bytes)
|
||||||
|
{
|
||||||
|
private_len = Some(vectags.len());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private_len
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
self.latest_person_list_event_data.remove(&person_list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Get all the pubkeys that the user subscribes to in any list
|
/// Get all the pubkeys that the user subscribes to in any list
|
||||||
pub fn get_subscribed_pubkeys(&self) -> Vec<PublicKey> {
|
pub fn get_subscribed_pubkeys(&self) -> Vec<PublicKey> {
|
||||||
// We subscribe to all people in all lists.
|
// We subscribe to all people in all lists.
|
||||||
@ -168,50 +176,13 @@ impl People {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get all the pubkeys in the Followed list
|
/// Is the person in the list? (returns false on error)
|
||||||
pub fn get_followed_pubkeys(&self) -> Vec<PublicKey> {
|
#[inline]
|
||||||
// We subscribe to all people in all lists.
|
pub fn is_person_in_list(&self, pubkey: &PublicKey, list: PersonList) -> bool {
|
||||||
// This is no longer synonomous with the ContactList list
|
GLOBALS
|
||||||
match GLOBALS
|
|
||||||
.storage
|
.storage
|
||||||
.get_people_in_list(PersonList::Followed, None)
|
.is_person_in_list(pubkey, list)
|
||||||
{
|
.unwrap_or(false)
|
||||||
Ok(people) => people,
|
|
||||||
Err(e) => {
|
|
||||||
tracing::error!("{}", e);
|
|
||||||
vec![]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get all the pubkeys that the user mutes
|
|
||||||
pub fn get_muted_pubkeys(&self) -> Vec<PublicKey> {
|
|
||||||
match GLOBALS.storage.get_people_in_list(PersonList::Muted, None) {
|
|
||||||
Ok(people) => people,
|
|
||||||
Err(e) => {
|
|
||||||
tracing::error!("{}", e);
|
|
||||||
vec![]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Is the given pubkey followed?
|
|
||||||
pub fn is_followed(&self, pubkey: &PublicKey) -> bool {
|
|
||||||
match GLOBALS
|
|
||||||
.storage
|
|
||||||
.is_person_in_list(pubkey, PersonList::Followed)
|
|
||||||
{
|
|
||||||
Ok(answer) => answer,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Is the given pubkey muted?
|
|
||||||
pub fn is_muted(&self, pubkey: &PublicKey) -> bool {
|
|
||||||
match GLOBALS.storage.is_person_in_list(pubkey, PersonList::Muted) {
|
|
||||||
Ok(answer) => answer,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get all the pubkeys that need relay lists (from the given set)
|
/// Get all the pubkeys that need relay lists (from the given set)
|
||||||
@ -609,92 +580,103 @@ impl People {
|
|||||||
.collect())
|
.collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn generate_contact_list_event(&self) -> Result<Event, Error> {
|
pub(crate) async fn generate_person_list_event(
|
||||||
let mut p_tags: Vec<Tag> = Vec::new();
|
&self,
|
||||||
|
person_list: PersonList,
|
||||||
let pubkeys = self.get_followed_pubkeys();
|
) -> Result<Event, Error> {
|
||||||
|
if !GLOBALS.signer.is_ready() {
|
||||||
for pubkey in &pubkeys {
|
return Err((ErrorKind::NoPrivateKey, file!(), line!()).into());
|
||||||
// Get their petname
|
|
||||||
let mut petname: Option<String> = None;
|
|
||||||
if let Some(person) = GLOBALS.storage.read_person(pubkey)? {
|
|
||||||
petname = person.petname.clone();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get their best relay
|
|
||||||
let relays = GLOBALS.storage.get_best_relays(*pubkey, Direction::Write)?;
|
|
||||||
let maybeurl = relays.get(0);
|
|
||||||
p_tags.push(Tag::Pubkey {
|
|
||||||
pubkey: (*pubkey).into(),
|
|
||||||
recommended_relay_url: maybeurl.map(|(u, _)| u.to_unchecked_url()),
|
|
||||||
petname,
|
|
||||||
trailing: Vec::new(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let public_key = match GLOBALS.signer.public_key() {
|
let my_pubkey = GLOBALS.signer.public_key().unwrap();
|
||||||
Some(pk) => pk,
|
|
||||||
None => return Err((ErrorKind::NoPrivateKey, file!(), line!()).into()), // not even a public key
|
|
||||||
};
|
|
||||||
|
|
||||||
// Get the content from our latest ContactList.
|
// Read the person list in two parts
|
||||||
// We don't use the data, but we shouldn't clobber it.
|
let public_people = GLOBALS
|
||||||
|
|
||||||
let content = match GLOBALS
|
|
||||||
.storage
|
.storage
|
||||||
.get_replaceable_event(public_key, EventKind::ContactList)?
|
.get_people_in_list(person_list, Some(true))?;
|
||||||
{
|
let private_people = GLOBALS
|
||||||
Some(c) => c.content,
|
.storage
|
||||||
None => "".to_owned(),
|
.get_people_in_list(person_list, Some(false))?;
|
||||||
|
|
||||||
|
// Determine the event kind
|
||||||
|
let kind = match person_list {
|
||||||
|
PersonList::Followed => EventKind::ContactList,
|
||||||
|
PersonList::Muted => EventKind::MuteList,
|
||||||
|
PersonList::Custom(_) => EventKind::CategorizedPeopleList,
|
||||||
};
|
};
|
||||||
|
|
||||||
let pre_event = PreEvent {
|
// Build public p-tags
|
||||||
pubkey: public_key,
|
let mut tags: Vec<Tag> = Vec::new();
|
||||||
created_at: Unixtime::now().unwrap(),
|
for pubkey in public_people.iter() {
|
||||||
kind: EventKind::ContactList,
|
// Only include petnames in the ContactList (which is only public people)
|
||||||
tags: p_tags,
|
let petname = if kind == EventKind::ContactList {
|
||||||
content,
|
if let Some(person) = GLOBALS.storage.read_person(pubkey)? {
|
||||||
};
|
person.petname.clone()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
GLOBALS.signer.sign_preevent(pre_event, None, None)
|
// Only include recommended relay urls in public entries, and not in the mute list
|
||||||
}
|
let recommended_relay_url = if kind != EventKind::MuteList {
|
||||||
|
let relays = GLOBALS.storage.get_best_relays(*pubkey, Direction::Write)?;
|
||||||
|
relays.get(0).map(|(u, _)| u.to_unchecked_url())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
pub(crate) async fn generate_mute_list_event(&self) -> Result<Event, Error> {
|
tags.push(Tag::Pubkey {
|
||||||
let mut p_tags: Vec<Tag> = Vec::new();
|
pubkey: pubkey.into(),
|
||||||
|
recommended_relay_url,
|
||||||
let muted_pubkeys = self.get_muted_pubkeys();
|
petname,
|
||||||
|
|
||||||
for muted_pubkey in &muted_pubkeys {
|
|
||||||
p_tags.push(Tag::Pubkey {
|
|
||||||
pubkey: (*muted_pubkey).into(),
|
|
||||||
recommended_relay_url: None,
|
|
||||||
petname: None,
|
|
||||||
trailing: vec![],
|
trailing: vec![],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let public_key = match GLOBALS.signer.public_key() {
|
// Add d-tag if using CategorizedPeopleList
|
||||||
Some(pk) => pk,
|
if matches!(person_list, PersonList::Custom(_)) {
|
||||||
None => return Err((ErrorKind::NoPrivateKey, file!(), line!()).into()), // not even a public key
|
tags.push(Tag::Identifier {
|
||||||
};
|
d: person_list.name(),
|
||||||
|
trailing: vec![],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Get the content from our latest MuteList.
|
let content = {
|
||||||
// We don't use the data, but we shouldn't clobber it (it is for private mutes
|
if kind == EventKind::ContactList {
|
||||||
// that we have not implemented yet)
|
match GLOBALS
|
||||||
|
.storage
|
||||||
let content = match GLOBALS
|
.get_replaceable_event(my_pubkey, EventKind::ContactList)?
|
||||||
.storage
|
{
|
||||||
.get_replaceable_event(public_key, EventKind::MuteList)?
|
Some(c) => c.content,
|
||||||
{
|
None => "".to_owned(),
|
||||||
Some(c) => c.content,
|
}
|
||||||
None => "".to_owned(),
|
} else {
|
||||||
|
// Build private p-tags (except for ContactList)
|
||||||
|
let mut private_p_tags: Vec<Tag> = Vec::new();
|
||||||
|
for pubkey in private_people.iter() {
|
||||||
|
private_p_tags.push(Tag::Pubkey {
|
||||||
|
pubkey: pubkey.into(),
|
||||||
|
recommended_relay_url: None,
|
||||||
|
petname: None,
|
||||||
|
trailing: vec![],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let private_tags_string = serde_json::to_string(&private_p_tags)?;
|
||||||
|
GLOBALS.signer.encrypt(
|
||||||
|
&my_pubkey,
|
||||||
|
&private_tags_string,
|
||||||
|
ContentEncryptionAlgorithm::Nip04,
|
||||||
|
)?
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let pre_event = PreEvent {
|
let pre_event = PreEvent {
|
||||||
pubkey: public_key,
|
pubkey: my_pubkey,
|
||||||
created_at: Unixtime::now().unwrap(),
|
created_at: Unixtime::now().unwrap(),
|
||||||
kind: EventKind::MuteList,
|
kind,
|
||||||
tags: p_tags,
|
tags,
|
||||||
content,
|
content,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -721,55 +703,17 @@ impl People {
|
|||||||
}
|
}
|
||||||
GLOBALS.ui_people_to_invalidate.write().push(*pubkey);
|
GLOBALS.ui_people_to_invalidate.write().push(*pubkey);
|
||||||
|
|
||||||
GLOBALS
|
GLOBALS.storage.set_person_list_last_edit_time(
|
||||||
.storage
|
PersonList::Followed,
|
||||||
.write_last_contact_list_edit(Unixtime::now().unwrap().0, Some(&mut txn))?;
|
Unixtime::now().unwrap().0,
|
||||||
|
Some(&mut txn),
|
||||||
|
)?;
|
||||||
|
|
||||||
txn.commit()?;
|
txn.commit()?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Follow all these public keys.
|
|
||||||
/// This does not publish any events.
|
|
||||||
pub(crate) fn follow_all(
|
|
||||||
&self,
|
|
||||||
pubkeys: &[PublicKey],
|
|
||||||
public: bool,
|
|
||||||
merge: bool,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
let mut txn = GLOBALS.storage.get_write_txn()?;
|
|
||||||
|
|
||||||
if !merge {
|
|
||||||
GLOBALS
|
|
||||||
.storage
|
|
||||||
.clear_person_list(PersonList::Followed, Some(&mut txn))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
for pubkey in pubkeys {
|
|
||||||
GLOBALS.storage.add_person_to_list(
|
|
||||||
pubkey,
|
|
||||||
PersonList::Followed,
|
|
||||||
public,
|
|
||||||
Some(&mut txn),
|
|
||||||
)?;
|
|
||||||
GLOBALS.ui_people_to_invalidate.write().push(*pubkey);
|
|
||||||
}
|
|
||||||
|
|
||||||
GLOBALS
|
|
||||||
.storage
|
|
||||||
.write_last_contact_list_edit(Unixtime::now().unwrap().0, Some(&mut txn))?;
|
|
||||||
|
|
||||||
txn.commit()?;
|
|
||||||
|
|
||||||
// Add the people to the relay_picker for picking
|
|
||||||
for pubkey in pubkeys.iter() {
|
|
||||||
GLOBALS.relay_picker.add_someone(pubkey.to_owned())?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Empty the following list.
|
/// Empty the following list.
|
||||||
/// This does not publish any events.
|
/// This does not publish any events.
|
||||||
pub(crate) fn follow_none(&self) -> Result<(), Error> {
|
pub(crate) fn follow_none(&self) -> Result<(), Error> {
|
||||||
@ -778,9 +722,11 @@ impl People {
|
|||||||
GLOBALS
|
GLOBALS
|
||||||
.storage
|
.storage
|
||||||
.clear_person_list(PersonList::Followed, Some(&mut txn))?;
|
.clear_person_list(PersonList::Followed, Some(&mut txn))?;
|
||||||
GLOBALS
|
GLOBALS.storage.set_person_list_last_edit_time(
|
||||||
.storage
|
PersonList::Followed,
|
||||||
.write_last_contact_list_edit(Unixtime::now().unwrap().0, Some(&mut txn))?;
|
Unixtime::now().unwrap().0,
|
||||||
|
Some(&mut txn),
|
||||||
|
)?;
|
||||||
|
|
||||||
txn.commit()?;
|
txn.commit()?;
|
||||||
|
|
||||||
@ -797,9 +743,11 @@ impl People {
|
|||||||
GLOBALS
|
GLOBALS
|
||||||
.storage
|
.storage
|
||||||
.clear_person_list(PersonList::Muted, Some(&mut txn))?;
|
.clear_person_list(PersonList::Muted, Some(&mut txn))?;
|
||||||
GLOBALS
|
GLOBALS.storage.set_person_list_last_edit_time(
|
||||||
.storage
|
PersonList::Muted,
|
||||||
.write_last_mute_list_edit(Unixtime::now().unwrap().0, Some(&mut txn))?;
|
Unixtime::now().unwrap().0,
|
||||||
|
Some(&mut txn),
|
||||||
|
)?;
|
||||||
|
|
||||||
txn.commit()?;
|
txn.commit()?;
|
||||||
|
|
||||||
@ -831,9 +779,11 @@ impl People {
|
|||||||
.remove_person_from_list(pubkey, PersonList::Muted, Some(&mut txn))?;
|
.remove_person_from_list(pubkey, PersonList::Muted, Some(&mut txn))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
GLOBALS
|
GLOBALS.storage.set_person_list_last_edit_time(
|
||||||
.storage
|
PersonList::Muted,
|
||||||
.write_last_mute_list_edit(Unixtime::now().unwrap().0, Some(&mut txn))?;
|
Unixtime::now().unwrap().0,
|
||||||
|
Some(&mut txn),
|
||||||
|
)?;
|
||||||
|
|
||||||
txn.commit()?;
|
txn.commit()?;
|
||||||
|
|
||||||
@ -842,39 +792,6 @@ impl People {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn mute_all(
|
|
||||||
&self,
|
|
||||||
pubkeys: &[PublicKey],
|
|
||||||
merge: bool,
|
|
||||||
public: bool,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
let mut txn = GLOBALS.storage.get_write_txn()?;
|
|
||||||
|
|
||||||
if !merge {
|
|
||||||
GLOBALS
|
|
||||||
.storage
|
|
||||||
.clear_person_list(PersonList::Muted, Some(&mut txn))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
for pubkey in pubkeys {
|
|
||||||
GLOBALS.storage.add_person_to_list(
|
|
||||||
pubkey,
|
|
||||||
PersonList::Muted,
|
|
||||||
public,
|
|
||||||
Some(&mut txn),
|
|
||||||
)?;
|
|
||||||
GLOBALS.ui_people_to_invalidate.write().push(*pubkey);
|
|
||||||
}
|
|
||||||
|
|
||||||
GLOBALS
|
|
||||||
.storage
|
|
||||||
.write_last_mute_list_edit(Unixtime::now().unwrap().0, Some(&mut txn))?;
|
|
||||||
|
|
||||||
txn.commit()?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns true if the date passed in is newer than what we already had
|
// Returns true if the date passed in is newer than what we already had
|
||||||
pub(crate) async fn update_relay_list_stamps(
|
pub(crate) async fn update_relay_list_stamps(
|
||||||
&self,
|
&self,
|
||||||
|
@ -2,6 +2,7 @@ use crate::comms::ToOverlordMessage;
|
|||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::filter::EventFilterAction;
|
use crate::filter::EventFilterAction;
|
||||||
use crate::globals::GLOBALS;
|
use crate::globals::GLOBALS;
|
||||||
|
use crate::people::PersonList;
|
||||||
use crate::person_relay::PersonRelay;
|
use crate::person_relay::PersonRelay;
|
||||||
use async_recursion::async_recursion;
|
use async_recursion::async_recursion;
|
||||||
use nostr_types::{
|
use nostr_types::{
|
||||||
@ -63,7 +64,11 @@ pub async fn process_new_event(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Spam filter (displayable and author is not followed)
|
// Spam filter (displayable and author is not followed)
|
||||||
if event.effective_kind().is_feed_displayable() && !GLOBALS.people.is_followed(&event.pubkey) {
|
if event.effective_kind().is_feed_displayable()
|
||||||
|
&& !GLOBALS
|
||||||
|
.people
|
||||||
|
.is_person_in_list(&event.pubkey, PersonList::Followed)
|
||||||
|
{
|
||||||
let author = GLOBALS.storage.read_person(&event.pubkey)?;
|
let author = GLOBALS.storage.read_person(&event.pubkey)?;
|
||||||
match crate::filter::filter(event.clone(), author) {
|
match crate::filter::filter(event.clone(), author) {
|
||||||
EventFilterAction::Allow => {}
|
EventFilterAction::Allow => {}
|
||||||
@ -212,62 +217,32 @@ pub async fn process_new_event(
|
|||||||
if event.kind == EventKind::ContactList {
|
if event.kind == EventKind::ContactList {
|
||||||
if let Some(pubkey) = GLOBALS.signer.public_key() {
|
if let Some(pubkey) = GLOBALS.signer.public_key() {
|
||||||
if event.pubkey == pubkey {
|
if event.pubkey == pubkey {
|
||||||
// We do not process our own contact list automatically.
|
// Update this data for the UI. We don't actually process the latest event
|
||||||
// Instead we only process it on user command.
|
// until the user gives the go ahead.
|
||||||
// See Overlord::update_following()
|
GLOBALS.people.update_latest_person_list_event_data();
|
||||||
//
|
|
||||||
// But we do update people.last_contact_list_asof and _size
|
|
||||||
if event.created_at.0
|
|
||||||
> GLOBALS
|
|
||||||
.people
|
|
||||||
.last_contact_list_asof
|
|
||||||
.load(Ordering::Relaxed)
|
|
||||||
{
|
|
||||||
GLOBALS
|
|
||||||
.people
|
|
||||||
.last_contact_list_asof
|
|
||||||
.store(event.created_at.0, Ordering::Relaxed);
|
|
||||||
let size = event
|
|
||||||
.tags
|
|
||||||
.iter()
|
|
||||||
.filter(|t| matches!(t, Tag::Pubkey { .. }))
|
|
||||||
.count();
|
|
||||||
GLOBALS
|
|
||||||
.people
|
|
||||||
.last_contact_list_size
|
|
||||||
.store(size, Ordering::Relaxed);
|
|
||||||
}
|
|
||||||
return Ok(());
|
|
||||||
} else {
|
} else {
|
||||||
process_somebody_elses_contact_list(event).await?;
|
process_somebody_elses_contact_list(event).await?;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
process_somebody_elses_contact_list(event).await?;
|
process_somebody_elses_contact_list(event).await?;
|
||||||
}
|
}
|
||||||
} else if event.kind == EventKind::MuteList {
|
} else if event.kind == EventKind::MuteList || event.kind == EventKind::CategorizedPeopleList {
|
||||||
if let Some(pubkey) = GLOBALS.signer.public_key() {
|
if let Some(pubkey) = GLOBALS.signer.public_key() {
|
||||||
if event.pubkey == pubkey {
|
if event.pubkey == pubkey {
|
||||||
// We do not process our own mute list automatically.
|
// Update this data for the UI. We don't actually process the latest event
|
||||||
// Instead we only process it on user command.
|
// until the user gives the go ahead.
|
||||||
// See Overlord::update_muted()
|
GLOBALS.people.update_latest_person_list_event_data();
|
||||||
//
|
}
|
||||||
// But we do update people.last_mute_list_asof and _size
|
}
|
||||||
if event.created_at.0 > GLOBALS.people.last_mute_list_asof.load(Ordering::Relaxed) {
|
|
||||||
GLOBALS
|
// Allocate a slot for this person list
|
||||||
.people
|
if event.kind == EventKind::CategorizedPeopleList {
|
||||||
.last_mute_list_asof
|
// get d-tag
|
||||||
.store(event.created_at.0, Ordering::Relaxed);
|
for tag in event.tags.iter() {
|
||||||
let size = event
|
if let Tag::Identifier { d, .. } = tag {
|
||||||
.tags
|
// This will allocate if missing, and will be ok if it exists
|
||||||
.iter()
|
PersonList::allocate(d, None)?;
|
||||||
.filter(|t| matches!(t, Tag::Pubkey { .. }))
|
|
||||||
.count();
|
|
||||||
GLOBALS
|
|
||||||
.people
|
|
||||||
.last_mute_list_size
|
|
||||||
.store(size, Ordering::Relaxed);
|
|
||||||
}
|
}
|
||||||
return Ok(());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if event.kind == EventKind::RelayList {
|
} else if event.kind == EventKind::RelayList {
|
||||||
|
61
gossip-lib/src/storage/migrations/deprecated.rs
Normal file
61
gossip-lib/src/storage/migrations/deprecated.rs
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
use super::Storage;
|
||||||
|
use crate::error::Error;
|
||||||
|
use heed::RwTxn;
|
||||||
|
use nostr_types::Unixtime;
|
||||||
|
|
||||||
|
impl Storage {
|
||||||
|
/// Write the user's last ContactList edit time
|
||||||
|
/// DEPRECATED - use set_person_list_last_edit_time instead
|
||||||
|
pub(in crate::storage) fn write_last_contact_list_edit<'a>(
|
||||||
|
&'a self,
|
||||||
|
when: i64,
|
||||||
|
rw_txn: Option<&mut RwTxn<'a>>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let bytes = when.to_be_bytes();
|
||||||
|
|
||||||
|
let f = |txn: &mut RwTxn<'a>| -> Result<(), Error> {
|
||||||
|
self.general
|
||||||
|
.put(txn, b"last_contact_list_edit", bytes.as_slice())?;
|
||||||
|
Ok(())
|
||||||
|
};
|
||||||
|
|
||||||
|
match rw_txn {
|
||||||
|
Some(txn) => f(txn)?,
|
||||||
|
None => {
|
||||||
|
let mut txn = self.env.write_txn()?;
|
||||||
|
f(&mut txn)?;
|
||||||
|
txn.commit()?;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read the user's last ContactList edit time
|
||||||
|
/// DEPRECATED - use get_person_list_last_edit_time instead
|
||||||
|
pub(in crate::storage) fn read_last_contact_list_edit(&self) -> Result<i64, Error> {
|
||||||
|
let txn = self.env.read_txn()?;
|
||||||
|
|
||||||
|
match self.general.get(&txn, b"last_contact_list_edit")? {
|
||||||
|
None => {
|
||||||
|
let now = Unixtime::now().unwrap();
|
||||||
|
Ok(now.0)
|
||||||
|
}
|
||||||
|
Some(bytes) => Ok(i64::from_be_bytes(bytes[..8].try_into().unwrap())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read the user's last MuteList edit time
|
||||||
|
/// DEPRECATED - use get_person_list_last_edit_time instead
|
||||||
|
pub(in crate::storage) fn read_last_mute_list_edit(&self) -> Result<i64, Error> {
|
||||||
|
let txn = self.env.read_txn()?;
|
||||||
|
|
||||||
|
match self.general.get(&txn, b"last_mute_list_edit")? {
|
||||||
|
None => {
|
||||||
|
let now = Unixtime::now().unwrap();
|
||||||
|
Ok(now.0)
|
||||||
|
}
|
||||||
|
Some(bytes) => Ok(i64::from_be_bytes(bytes[..8].try_into().unwrap())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,5 @@
|
|||||||
|
mod deprecated;
|
||||||
|
|
||||||
use super::types::{
|
use super::types::{
|
||||||
Person2, PersonList1, PersonRelay1, Settings1, Settings2, Theme1, ThemeVariant1,
|
Person2, PersonList1, PersonRelay1, Settings1, Settings2, Theme1, ThemeVariant1,
|
||||||
};
|
};
|
||||||
@ -10,7 +12,7 @@ use speedy::{Readable, Writable};
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
impl Storage {
|
impl Storage {
|
||||||
const MAX_MIGRATION_LEVEL: u32 = 14;
|
const MAX_MIGRATION_LEVEL: u32 = 15;
|
||||||
|
|
||||||
pub(super) fn migrate(&self, mut level: u32) -> Result<(), Error> {
|
pub(super) fn migrate(&self, mut level: u32) -> Result<(), Error> {
|
||||||
if level > Self::MAX_MIGRATION_LEVEL {
|
if level > Self::MAX_MIGRATION_LEVEL {
|
||||||
@ -137,6 +139,10 @@ impl Storage {
|
|||||||
tracing::info!("{prefix}: removing a retired setting...");
|
tracing::info!("{prefix}: removing a retired setting...");
|
||||||
self.remove_setting_custom_person_list_names(txn)?;
|
self.remove_setting_custom_person_list_names(txn)?;
|
||||||
}
|
}
|
||||||
|
14 => {
|
||||||
|
tracing::info!("{prefix}: moving person list last edit times...");
|
||||||
|
self.move_person_list_last_edit_times(txn)?;
|
||||||
|
}
|
||||||
_ => panic!("Unreachable migration level"),
|
_ => panic!("Unreachable migration level"),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -603,4 +609,15 @@ impl Storage {
|
|||||||
self.general.delete(txn, b"custom_person_list_names")?;
|
self.general.delete(txn, b"custom_person_list_names")?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn move_person_list_last_edit_times<'a>(
|
||||||
|
&'a self,
|
||||||
|
txn: &mut RwTxn<'a>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let mut edit_times: HashMap<PersonList1, i64> = HashMap::new();
|
||||||
|
edit_times.insert(PersonList1::Followed, self.read_last_contact_list_edit()?);
|
||||||
|
edit_times.insert(PersonList1::Muted, self.read_last_mute_list_edit()?);
|
||||||
|
self.write_person_lists_last_edit_times(edit_times, Some(txn))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -538,17 +538,17 @@ impl Storage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write the user's last ContactList edit time
|
/// Write the user's last PersonList edit times
|
||||||
pub fn write_last_contact_list_edit<'a>(
|
pub fn write_person_lists_last_edit_times<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
when: i64,
|
times: HashMap<PersonList, i64>,
|
||||||
rw_txn: Option<&mut RwTxn<'a>>,
|
rw_txn: Option<&mut RwTxn<'a>>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let bytes = when.to_be_bytes();
|
let bytes = times.write_to_vec()?;
|
||||||
|
|
||||||
let f = |txn: &mut RwTxn<'a>| -> Result<(), Error> {
|
let f = |txn: &mut RwTxn<'a>| -> Result<(), Error> {
|
||||||
self.general
|
self.general
|
||||||
.put(txn, b"last_contact_list_edit", bytes.as_slice())?;
|
.put(txn, b"person_lists_last_edit_times", bytes.as_slice())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -565,57 +565,32 @@ impl Storage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Read the user's last ContactList edit time
|
/// Read the user's last ContactList edit time
|
||||||
pub fn read_last_contact_list_edit(&self) -> Result<i64, Error> {
|
pub fn read_person_lists_last_edit_times(&self) -> Result<HashMap<PersonList, i64>, Error> {
|
||||||
let txn = self.env.read_txn()?;
|
let txn = self.env.read_txn()?;
|
||||||
|
|
||||||
match self.general.get(&txn, b"last_contact_list_edit")? {
|
match self.general.get(&txn, b"person_lists_last_edit_times")? {
|
||||||
None => {
|
None => Ok(HashMap::new()),
|
||||||
let now = Unixtime::now().unwrap();
|
Some(bytes) => Ok(HashMap::<PersonList, i64>::read_from_buffer(bytes)?),
|
||||||
self.write_last_contact_list_edit(now.0, None)?;
|
|
||||||
Ok(now.0)
|
|
||||||
}
|
|
||||||
Some(bytes) => Ok(i64::from_be_bytes(bytes[..8].try_into().unwrap())),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write the user's last MuteList edit time
|
/// Set a person list last edit time
|
||||||
pub fn write_last_mute_list_edit<'a>(
|
pub fn set_person_list_last_edit_time<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
when: i64,
|
list: PersonList,
|
||||||
|
time: i64,
|
||||||
rw_txn: Option<&mut RwTxn<'a>>,
|
rw_txn: Option<&mut RwTxn<'a>>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let bytes = when.to_be_bytes();
|
let mut lists = self.read_person_lists_last_edit_times()?;
|
||||||
|
let _ = lists.insert(list, time);
|
||||||
let f = |txn: &mut RwTxn<'a>| -> Result<(), Error> {
|
self.write_person_lists_last_edit_times(lists, rw_txn)?;
|
||||||
self.general
|
|
||||||
.put(txn, b"last_mute_list_edit", bytes.as_slice())?;
|
|
||||||
Ok(())
|
|
||||||
};
|
|
||||||
|
|
||||||
match rw_txn {
|
|
||||||
Some(txn) => f(txn)?,
|
|
||||||
None => {
|
|
||||||
let mut txn = self.env.write_txn()?;
|
|
||||||
f(&mut txn)?;
|
|
||||||
txn.commit()?;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read the user's last MuteList edit time
|
/// Get a person list last edit time
|
||||||
pub fn read_last_mute_list_edit(&self) -> Result<i64, Error> {
|
pub fn get_person_list_last_edit_time(&self, list: PersonList) -> Result<Option<i64>, Error> {
|
||||||
let txn = self.env.read_txn()?;
|
let lists = self.read_person_lists_last_edit_times()?;
|
||||||
|
Ok(lists.get(&list).copied())
|
||||||
match self.general.get(&txn, b"last_mute_list_edit")? {
|
|
||||||
None => {
|
|
||||||
let now = Unixtime::now().unwrap();
|
|
||||||
self.write_last_mute_list_edit(now.0, None)?;
|
|
||||||
Ok(now.0)
|
|
||||||
}
|
|
||||||
Some(bytes) => Ok(i64::from_be_bytes(bytes[..8].try_into().unwrap())),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write a flag, whether the user is only following people with no account (or not)
|
/// Write a flag, whether the user is only following people with no account (or not)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use crate::error::{Error, ErrorKind};
|
use crate::error::{Error, ErrorKind};
|
||||||
use crate::globals::GLOBALS;
|
use crate::globals::GLOBALS;
|
||||||
use heed::RwTxn;
|
use heed::RwTxn;
|
||||||
|
use nostr_types::EventKind;
|
||||||
use speedy::{Readable, Writable};
|
use speedy::{Readable, Writable};
|
||||||
|
|
||||||
/// Lists people can be added to
|
/// Lists people can be added to
|
||||||
@ -51,14 +52,34 @@ impl PersonList1 {
|
|||||||
let mut output: Vec<(PersonList1, String)> = vec![];
|
let mut output: Vec<(PersonList1, String)> = vec![];
|
||||||
let map = GLOBALS.storage.read_setting_custom_person_list_map();
|
let map = GLOBALS.storage.read_setting_custom_person_list_map();
|
||||||
for (k, v) in map.iter() {
|
for (k, v) in map.iter() {
|
||||||
output.push((PersonList1::Custom(*k), v.clone()));
|
match k {
|
||||||
|
0 => output.push((PersonList1::Muted, v.clone())),
|
||||||
|
1 => output.push((PersonList1::Followed, v.clone())),
|
||||||
|
_ => output.push((PersonList1::Custom(*k), v.clone())),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
output
|
output
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Allocate a new PersonList1 with the given name
|
/// Allocate a new PersonList1 with the given name
|
||||||
pub fn allocate(name: &str, txn: Option<&mut RwTxn<'_>>) -> Result<PersonList1, Error> {
|
pub fn allocate(name: &str, txn: Option<&mut RwTxn<'_>>) -> Result<PersonList1, Error> {
|
||||||
|
// Do not allocate for well-known names
|
||||||
|
if name == "Followed" {
|
||||||
|
return Ok(PersonList1::Followed);
|
||||||
|
} else if name == "Muted" {
|
||||||
|
return Ok(PersonList1::Muted);
|
||||||
|
}
|
||||||
|
|
||||||
let mut map = GLOBALS.storage.read_setting_custom_person_list_map();
|
let mut map = GLOBALS.storage.read_setting_custom_person_list_map();
|
||||||
|
|
||||||
|
// Check if it already exists to prevent duplicates
|
||||||
|
for (k, v) in map.iter() {
|
||||||
|
if v == name {
|
||||||
|
return Ok(PersonList1::Custom(*k));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find a slot and allocate
|
||||||
for i in 2..255 {
|
for i in 2..255 {
|
||||||
if map.contains_key(&i) {
|
if map.contains_key(&i) {
|
||||||
continue;
|
continue;
|
||||||
@ -69,6 +90,7 @@ impl PersonList1 {
|
|||||||
.write_setting_custom_person_list_map(&map, txn)?;
|
.write_setting_custom_person_list_map(&map, txn)?;
|
||||||
return Ok(PersonList1::Custom(i));
|
return Ok(PersonList1::Custom(i));
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(ErrorKind::NoSlotsRemaining.into())
|
Err(ErrorKind::NoSlotsRemaining.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,6 +127,15 @@ impl PersonList1 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the event kind matching this PersonList1
|
||||||
|
pub fn event_kind(&self) -> EventKind {
|
||||||
|
match *self {
|
||||||
|
PersonList1::Followed => EventKind::ContactList,
|
||||||
|
PersonList1::Muted => EventKind::MuteList,
|
||||||
|
PersonList1::Custom(_) => EventKind::CategorizedPeopleList,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Should we subscribe to events from people in this list?
|
/// Should we subscribe to events from people in this list?
|
||||||
pub fn subscribe(&self) -> bool {
|
pub fn subscribe(&self) -> bool {
|
||||||
!matches!(*self, PersonList1::Muted)
|
!matches!(*self, PersonList1::Muted)
|
||||||
|
Loading…
Reference in New Issue
Block a user