use super::prelude::*;
use nostr::{Filter, Kind, SubscriptionId, Timestamp};
use std::time::Duration;
pub trait CustomFeeds {
fn feed_node(&self, pubk: &PublicKey, name: Option<&str>) -> NamedNode;
fn feed_init(
&self,
node: NamedNodeRef,
title: Option<&str>,
) -> Result<(), RdfStoreError>;
fn feed_attach_event(
&self,
feed_node: NamedNodeRef,
event_node: NamedNodeRef,
) -> Result<(), RdfStoreError>;
fn feed_filters(&self, node: NamedNodeRef)
-> Vec<(Filter, SubscriptionId)>;
fn feeds_list(
&self,
pubk: Option<&PublicKey>,
) -> Result<Vec<(NamedNode, String)>, RdfStoreError>;
fn feed_follow_hashtag(
&self,
feed_node: NamedNodeRef,
hashtag: &str,
) -> Result<(), RdfStoreError>;
fn feed_unfollow_hashtag(
&self,
feed_node: NamedNodeRef,
hashtag: &str,
) -> Result<(), RdfStoreError>;
fn feed_followed_hashtags(
&self,
feed_node: NamedNodeRef,
) -> Result<Vec<String>, RdfStoreError>;
fn feeds_following_hashtag(
&self,
hashtag: &str,
) -> Result<Vec<NamedNode>, RdfStoreError>;
fn feed_hashtag_is_followed(
&self,
feed_node: NamedNodeRef,
hashtag: &str,
) -> Result<bool, RdfStoreError>;
fn feed_watch_regexp(
&self,
feed_node: NamedNodeRef,
regexp: &str,
) -> Result<(), RdfStoreError>;
}
const PRED_FEED_TITLE: NamedNodeRef<'static> =
NamedNodeRef::new_unchecked("https://w3id.org/nostr#feed_title");
const PRED_FEED_EVENT: NamedNodeRef<'static> =
NamedNodeRef::new_unchecked("https://w3id.org/nostr#feed_event");
const PRED_FOLLOWS_HASHTAG: NamedNodeRef<'static> =
NamedNodeRef::new_unchecked("https://w3id.org/nostr#follows_hashtag");
const PRED_WATCH_REGEXP_I: NamedNodeRef<'static> =
NamedNodeRef::new_unchecked("https://w3id.org/nostr#watches_regexp_i");
#[allow(dead_code)]
const PRED_WATCH_REGEXP_S: NamedNodeRef<'static> =
NamedNodeRef::new_unchecked("https://w3id.org/nostr#watches_regexp_s");
impl CustomFeeds for RdfEventsStore {
fn feed_node(&self, pubk: &PublicKey, name: Option<&str>) -> NamedNode {
pubk.named_node_with_f(name.unwrap_or("feed-default"))
.unwrap_or(NamedNode::new_unchecked("urn:feeds:nostralink"))
}
fn feed_init(
&self,
node: NamedNodeRef,
feed_title: Option<&str>,
) -> Result<(), RdfStoreError> {
if let Some(title) = feed_title {
self.store.insert(QuadRef::new(
node,
PRED_FEED_TITLE,
&Literal::from(title),
&GraphName::DefaultGraph,
))?;
}
Ok(())
}
fn feed_attach_event(
&self,
feed_node: NamedNodeRef,
event_node: NamedNodeRef,
) -> Result<(), RdfStoreError> {
self.store.insert(QuadRef::new(
event_node,
PRED_FEED_EVENT,
feed_node,
&GraphName::DefaultGraph,
))?;
Ok(())
}
fn feeds_list(
&self,
_pubk: Option<&PublicKey>,
) -> Result<Vec<(NamedNode, String)>, RdfStoreError> {
Ok(self
.store
.quads_for_pattern(None, Some(PRED_FEED_TITLE), None, None)
.collect::<Result<Vec<_>, _>>()?
.iter()
.filter_map(|quad| match &quad.object {
Term::Literal(title) => match &quad.subject {
Subject::NamedNode(node) => {
Some((node.clone(), title.value().to_string()))
}
_ => None,
},
_ => None,
})
.collect())
}
fn feed_filters(
&self,
node: NamedNodeRef,
) -> Vec<(Filter, SubscriptionId)> {
let mut filters = Vec::new();
if let Ok(hashtags) = self.feed_followed_hashtags(node) {
filters.push((
Filter::new().kind(Kind::TextNote).hashtags(hashtags).since(
Timestamp::now() - Duration::from_secs(3600 * 24 * 3),
),
SubscriptionId::new(node.to_string()),
));
}
filters
}
fn feed_follow_hashtag(
&self,
feed_node: NamedNodeRef,
hashtag: &str,
) -> Result<(), RdfStoreError> {
self.store.insert(QuadRef::new(
feed_node,
PRED_FOLLOWS_HASHTAG,
&Literal::from(hashtag),
&GraphName::DefaultGraph,
))?;
Ok(())
}
fn feed_unfollow_hashtag(
&self,
feed_node: NamedNodeRef,
hashtag: &str,
) -> Result<(), RdfStoreError> {
self.store.remove(QuadRef::new(
feed_node,
PRED_FOLLOWS_HASHTAG,
&Literal::from(hashtag),
&GraphName::DefaultGraph,
))?;
Ok(())
}
fn feed_followed_hashtags(
&self,
feed_node: NamedNodeRef,
) -> Result<Vec<String>, RdfStoreError> {
Ok(self
.store
.quads_for_pattern(
Some(feed_node.into()),
Some(PRED_FOLLOWS_HASHTAG),
None,
None,
)
.collect::<Result<Vec<_>, _>>()
.map_err(|_| RdfStoreError::QueryError)?
.into_iter()
.filter_map(|quad| match &quad.object {
Term::Literal(hashtag) => Some(hashtag.value().to_string()),
_ => None,
})
.collect())
}
fn feeds_following_hashtag(
&self,
hashtag: &str,
) -> Result<Vec<NamedNode>, RdfStoreError> {
Ok(self
.store
.quads_for_pattern(
None,
Some(PRED_FOLLOWS_HASHTAG),
Some((&Literal::from(hashtag)).into()),
None,
)
.collect::<Result<Vec<_>, _>>()
.map_err(|_| RdfStoreError::QueryError)?
.into_iter()
.filter_map(|quad| match &quad.subject {
Subject::NamedNode(node) => Some(node.clone()),
_ => None,
})
.collect())
}
fn feed_hashtag_is_followed(
&self,
feed_node: NamedNodeRef,
hashtag: &str,
) -> Result<bool, RdfStoreError> {
Ok(!self
.store
.quads_for_pattern(
Some(feed_node.into()),
Some(PRED_FOLLOWS_HASHTAG),
Some((&Literal::from(hashtag)).into()),
None,
)
.collect::<Result<Vec<_>, _>>()?
.is_empty())
}
fn feed_watch_regexp(
&self,
feed_node: NamedNodeRef,
regexp: &str,
) -> Result<(), RdfStoreError> {
self.store.insert(QuadRef::new(
feed_node,
PRED_WATCH_REGEXP_I,
&Literal::from(regexp),
&GraphName::DefaultGraph,
))?;
Ok(())
}
}