add invite

This commit is contained in:
2026-09-29 14:31:43 +07:00
parent 1a123f6d03
commit 45daeba8a6
4 changed files with 134 additions and 0 deletions
Generated
+1
View File
@@ -1291,6 +1291,7 @@ dependencies = [
"log",
"nostr-memory",
"nostr-sdk",
"regex",
"serde_json",
"smallvec",
"smol",
+1
View File
@@ -10,6 +10,7 @@ state = { path = "../state" }
gpui.workspace = true
nostr-sdk.workspace = true
regex = "1"
anyhow.workspace = true
data-encoding.workspace = true
+131
View File
@@ -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
}
+1
View File
@@ -17,6 +17,7 @@ use crate::rekey::WatchRegistry;
pub mod cache;
mod community;
pub mod discover;
pub mod history;
mod rekey;
mod sync;