kind.rs (6735B)
1 use crate::error::{Error, FilterError}; 2 use crate::filter; 3 use crate::filter::FilterState; 4 use crate::timeline::Timeline; 5 use enostr::{Filter, Pubkey}; 6 use nostrdb::{Ndb, Transaction}; 7 use serde::{Deserialize, Serialize}; 8 use std::{borrow::Cow, fmt::Display}; 9 use tracing::{error, warn}; 10 11 #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] 12 pub enum PubkeySource { 13 Explicit(Pubkey), 14 DeckAuthor, 15 } 16 17 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 18 pub enum ListKind { 19 Contact(PubkeySource), 20 } 21 22 impl PubkeySource { 23 pub fn to_pubkey<'a>(&'a self, deck_author: &'a Pubkey) -> &'a Pubkey { 24 match self { 25 PubkeySource::Explicit(pk) => pk, 26 PubkeySource::DeckAuthor => deck_author, 27 } 28 } 29 } 30 31 impl ListKind { 32 pub fn pubkey_source(&self) -> Option<&PubkeySource> { 33 match self { 34 ListKind::Contact(pk_src) => Some(pk_src), 35 } 36 } 37 } 38 39 /// 40 /// What kind of timeline is it? 41 /// - Follow List 42 /// - Notifications 43 /// - DM 44 /// - filter 45 /// - ... etc 46 /// 47 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 48 pub enum TimelineKind { 49 List(ListKind), 50 51 Notifications(PubkeySource), 52 53 Profile(PubkeySource), 54 55 Universe, 56 57 /// Generic filter 58 Generic, 59 60 Hashtag(String), 61 } 62 63 impl Display for TimelineKind { 64 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 65 match self { 66 TimelineKind::List(ListKind::Contact(_src)) => f.write_str("Contacts"), 67 TimelineKind::Generic => f.write_str("Timeline"), 68 TimelineKind::Notifications(_) => f.write_str("Notifications"), 69 TimelineKind::Profile(_) => f.write_str("Profile"), 70 TimelineKind::Universe => f.write_str("Universe"), 71 TimelineKind::Hashtag(_) => f.write_str("Hashtag"), 72 } 73 } 74 } 75 76 impl TimelineKind { 77 pub fn pubkey_source(&self) -> Option<&PubkeySource> { 78 match self { 79 TimelineKind::List(list_kind) => list_kind.pubkey_source(), 80 TimelineKind::Notifications(pk_src) => Some(pk_src), 81 TimelineKind::Profile(pk_src) => Some(pk_src), 82 TimelineKind::Universe => None, 83 TimelineKind::Generic => None, 84 TimelineKind::Hashtag(_ht) => None, 85 } 86 } 87 88 pub fn contact_list(pk: PubkeySource) -> Self { 89 TimelineKind::List(ListKind::Contact(pk)) 90 } 91 92 pub fn is_contacts(&self) -> bool { 93 matches!(self, TimelineKind::List(ListKind::Contact(_))) 94 } 95 96 pub fn profile(pk: PubkeySource) -> Self { 97 TimelineKind::Profile(pk) 98 } 99 100 pub fn is_notifications(&self) -> bool { 101 matches!(self, TimelineKind::Notifications(_)) 102 } 103 104 pub fn notifications(pk: PubkeySource) -> Self { 105 TimelineKind::Notifications(pk) 106 } 107 108 pub fn into_timeline(self, ndb: &Ndb, default_user: Option<&[u8; 32]>) -> Option<Timeline> { 109 match self { 110 TimelineKind::Universe => Some(Timeline::new( 111 TimelineKind::Universe, 112 FilterState::ready(vec![Filter::new() 113 .kinds([1]) 114 .limit(filter::default_limit()) 115 .build()]), 116 )), 117 118 TimelineKind::Generic => { 119 warn!("you can't convert a TimelineKind::Generic to a Timeline"); 120 None 121 } 122 123 TimelineKind::Profile(pk_src) => { 124 let pk = match &pk_src { 125 PubkeySource::DeckAuthor => default_user?, 126 PubkeySource::Explicit(pk) => pk.bytes(), 127 }; 128 129 let filter = Filter::new() 130 .authors([pk]) 131 .kinds([1]) 132 .limit(filter::default_limit()) 133 .build(); 134 135 Some(Timeline::new( 136 TimelineKind::profile(pk_src), 137 FilterState::ready(vec![filter]), 138 )) 139 } 140 141 TimelineKind::Notifications(pk_src) => { 142 let pk = match &pk_src { 143 PubkeySource::DeckAuthor => default_user?, 144 PubkeySource::Explicit(pk) => pk.bytes(), 145 }; 146 147 let notifications_filter = Filter::new() 148 .pubkeys([pk]) 149 .kinds([1]) 150 .limit(crate::filter::default_limit()) 151 .build(); 152 153 Some(Timeline::new( 154 TimelineKind::notifications(pk_src), 155 FilterState::ready(vec![notifications_filter]), 156 )) 157 } 158 159 TimelineKind::Hashtag(hashtag) => Some(Timeline::hashtag(hashtag)), 160 161 TimelineKind::List(ListKind::Contact(pk_src)) => { 162 let pk = match &pk_src { 163 PubkeySource::DeckAuthor => default_user?, 164 PubkeySource::Explicit(pk) => pk.bytes(), 165 }; 166 167 let contact_filter = Filter::new().authors([pk]).kinds([3]).limit(1).build(); 168 169 let txn = Transaction::new(ndb).expect("txn"); 170 let results = ndb 171 .query(&txn, &[contact_filter.clone()], 1) 172 .expect("contact query failed?"); 173 174 if results.is_empty() { 175 return Some(Timeline::new( 176 TimelineKind::contact_list(pk_src), 177 FilterState::needs_remote(vec![contact_filter.clone()]), 178 )); 179 } 180 181 match Timeline::contact_list(&results[0].note, pk_src.clone()) { 182 Err(Error::Filter(FilterError::EmptyContactList)) => Some(Timeline::new( 183 TimelineKind::contact_list(pk_src), 184 FilterState::needs_remote(vec![contact_filter]), 185 )), 186 Err(e) => { 187 error!("Unexpected error: {e}"); 188 None 189 } 190 Ok(tl) => Some(tl), 191 } 192 } 193 } 194 } 195 196 pub fn to_title(&self) -> Cow<'static, str> { 197 match self { 198 TimelineKind::List(list_kind) => match list_kind { 199 ListKind::Contact(_pubkey_source) => Cow::Borrowed("Contacts"), 200 }, 201 TimelineKind::Notifications(_pubkey_source) => Cow::Borrowed("Notifications"), 202 TimelineKind::Profile(_pubkey_source) => Cow::Borrowed("Profile"), 203 TimelineKind::Universe => Cow::Borrowed("Universe"), 204 TimelineKind::Generic => Cow::Borrowed("Custom"), 205 TimelineKind::Hashtag(hashtag) => Cow::Owned(format!("#{}", hashtag)), 206 } 207 } 208 }