From 45daeba8a64410ca8a3e9daf3252151e5338d556 Mon Sep 17 00:00:00 2001 From: Ren Amamiya Date: Tue, 29 Sep 2026 14:31:43 +0700 Subject: [PATCH] add invite --- Cargo.lock | 1 + crates/community/Cargo.toml | 1 + crates/community/src/discover.rs | 131 +++++++++++++++++++++++++++++++ crates/community/src/lib.rs | 1 + 4 files changed, 134 insertions(+) create mode 100644 crates/community/src/discover.rs diff --git a/Cargo.lock b/Cargo.lock index fe98f57e..7a072b53 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1291,6 +1291,7 @@ dependencies = [ "log", "nostr-memory", "nostr-sdk", + "regex", "serde_json", "smallvec", "smol", diff --git a/crates/community/Cargo.toml b/crates/community/Cargo.toml index bbc23794..cf001397 100644 --- a/crates/community/Cargo.toml +++ b/crates/community/Cargo.toml @@ -10,6 +10,7 @@ state = { path = "../state" } gpui.workspace = true nostr-sdk.workspace = true +regex = "1" anyhow.workspace = true data-encoding.workspace = true diff --git a/crates/community/src/discover.rs b/crates/community/src/discover.rs new file mode 100644 index 00000000..2ea0391c --- /dev/null +++ b/crates/community/src/discover.rs @@ -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 = 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 { + 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 { + 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 { + 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 = 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 { + let authors: BTreeMap = 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, deletes: &[Event]) -> BTreeSet { + 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 +} diff --git a/crates/community/src/lib.rs b/crates/community/src/lib.rs index 67fbae2e..7e4feb89 100644 --- a/crates/community/src/lib.rs +++ b/crates/community/src/lib.rs @@ -17,6 +17,7 @@ use crate::rekey::WatchRegistry; pub mod cache; mod community; +pub mod discover; pub mod history; mod rekey; mod sync;