add invite
This commit is contained in:
Generated
+1
@@ -1291,6 +1291,7 @@ dependencies = [
|
|||||||
"log",
|
"log",
|
||||||
"nostr-memory",
|
"nostr-memory",
|
||||||
"nostr-sdk",
|
"nostr-sdk",
|
||||||
|
"regex",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"smol",
|
"smol",
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ state = { path = "../state" }
|
|||||||
|
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
nostr-sdk.workspace = true
|
nostr-sdk.workspace = true
|
||||||
|
regex = "1"
|
||||||
|
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
data-encoding.workspace = true
|
data-encoding.workspace = true
|
||||||
|
|||||||
@@ -0,0 +1,131 @@
|
|||||||
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
|
use concord::cord05;
|
||||||
|
use nostr_sdk::prelude::*;
|
||||||
|
use regex::Regex;
|
||||||
|
|
||||||
|
pub const KIND_COMMUNITY_ANNOUNCEMENT: u16 = 3314;
|
||||||
|
|
||||||
|
static INVITE_URL: LazyLock<Regex> = LazyLock::new(|| {
|
||||||
|
Regex::new(
|
||||||
|
r"(?i)(?:https?://\S+?/invite/naddr1[0-9a-z]+#[A-Za-z0-9_-]+)|(?:naddr1[0-9a-z]+#[A-Za-z0-9_-]+)",
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
});
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Invite {
|
||||||
|
pub invite_url: String,
|
||||||
|
pub link_signer: PublicKey,
|
||||||
|
pub source: Event,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Invite {
|
||||||
|
pub fn from_event(event: &Event) -> Option<Invite> {
|
||||||
|
let invite_url = extract_invite_urls(&event.content).into_iter().next()?;
|
||||||
|
let link_signer = cord05::parse_link(&invite_url).ok()?.link_signer;
|
||||||
|
|
||||||
|
Some(Invite {
|
||||||
|
invite_url,
|
||||||
|
link_signer,
|
||||||
|
source: event.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn extract_invite_urls(text: &str) -> Vec<String> {
|
||||||
|
let mut urls = Vec::new();
|
||||||
|
let mut seen = BTreeSet::new();
|
||||||
|
|
||||||
|
for found in INVITE_URL.find_iter(text) {
|
||||||
|
let candidate = found.as_str();
|
||||||
|
|
||||||
|
let Ok(link) = cord05::parse_link(candidate) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
if seen.insert(link.link_signer) {
|
||||||
|
urls.push(candidate.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
urls
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_community_announcement(invite_url: &str) -> Option<EventBuilder> {
|
||||||
|
if cord05::parse_link(invite_url).is_err() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(EventBuilder::new(
|
||||||
|
Kind::Custom(KIND_COMMUNITY_ANNOUNCEMENT),
|
||||||
|
invite_url.trim(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_announcement_deletion(ids: &[EventId]) -> EventBuilder {
|
||||||
|
let mut tags: Vec<Tag> = ids.iter().map(|id| Tag::event(*id)).collect();
|
||||||
|
tags.push(Tag::custom("k", [KIND_COMMUNITY_ANNOUNCEMENT.to_string()]));
|
||||||
|
|
||||||
|
EventBuilder::new(Kind::EventDeletion, "").tags(tags)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fold_announcements(announcements: &[Event], deletes: &[Event]) -> Vec<Invite> {
|
||||||
|
let authors: BTreeMap<EventId, PublicKey> = announcements
|
||||||
|
.iter()
|
||||||
|
.filter(|event| event.kind == Kind::Custom(KIND_COMMUNITY_ANNOUNCEMENT))
|
||||||
|
.map(|event| (event.id, event.pubkey))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let deleted = deleted_ids(&authors, deletes);
|
||||||
|
|
||||||
|
let mut ordered: Vec<&Event> = announcements.iter().collect();
|
||||||
|
ordered.sort_by_key(|event| std::cmp::Reverse(event.created_at));
|
||||||
|
|
||||||
|
let mut seen = BTreeSet::new();
|
||||||
|
let mut folded = Vec::new();
|
||||||
|
|
||||||
|
for event in ordered {
|
||||||
|
if deleted.contains(&event.id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(invite) = Invite::from_event(event) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
if seen.insert(invite.link_signer) {
|
||||||
|
folded.push(invite);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
folded
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deleted_ids(authors: &BTreeMap<EventId, PublicKey>, deletes: &[Event]) -> BTreeSet<EventId> {
|
||||||
|
let mut deleted = BTreeSet::new();
|
||||||
|
|
||||||
|
for event in deletes
|
||||||
|
.iter()
|
||||||
|
.filter(|event| event.kind == Kind::EventDeletion)
|
||||||
|
{
|
||||||
|
for tag in event.tags.iter() {
|
||||||
|
let fields = tag.as_slice();
|
||||||
|
|
||||||
|
if fields.len() < 2 || fields[0] != "e" {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Ok(id) = EventId::from_hex(&fields[1]) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
if authors.get(&id) == Some(&event.pubkey) {
|
||||||
|
deleted.insert(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deleted
|
||||||
|
}
|
||||||
@@ -17,6 +17,7 @@ use crate::rekey::WatchRegistry;
|
|||||||
|
|
||||||
pub mod cache;
|
pub mod cache;
|
||||||
mod community;
|
mod community;
|
||||||
|
pub mod discover;
|
||||||
pub mod history;
|
pub mod history;
|
||||||
mod rekey;
|
mod rekey;
|
||||||
mod sync;
|
mod sync;
|
||||||
|
|||||||
Reference in New Issue
Block a user