notedeck

One damus client to rule them all
git clone git://jb55.com/notedeck
Log | Files | Refs | README | LICENSE

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 }