use nostr::prelude::*; /// ngit / GitWorkshop cover-note extension (kind 1624): a markdown note /// attached to an issue, patch or PR by its author or a repository /// maintainer. Not part of the NIP-34 draft; read support for interop. pub const COVER_NOTE_KIND: Kind = Kind::Custom(1624); /// Whether a kind-1985 label event is a valid annotation of `root`: it /// references the root via a lowercase `e` tag and was authored by the root /// author or a maintainer. fn label_targets_root(event: &Event, root: &Event, maintainers: &[PublicKey]) -> bool { if event.kind != Kind::Label { return false; } if event.pubkey != root.pubkey && !maintainers.contains(&event.pubkey) { return false; } let root_id = root.id.to_hex(); event .tags .iter() .any(|tag| tag.kind() == "e" && tag.content().is_some_and(|content| content == root_id)) } /// Whether a kind-1985 label event declares the `#t` namespace and carries at /// least one `["l", "", "#t"]` label. fn has_hashtag_labels(event: &Event) -> bool { event.tags.iter().any(|tag| tag.as_slice() == ["L", "#t"]) && event.tags.iter().any(|tag| { let slice = tag.as_slice(); slice.len() >= 3 && slice[0] == "l" && slice[2] == "#t" && !slice[1].is_empty() }) } /// The effective hashtag labels of `root`: the `t` tags on the event itself /// (self-reported by its author) plus all labels attached via authorized /// NIP-32 kind-1985 events in the `#t` namespace. Labels are additive — all /// valid label events contribute (no latest-wins semantics). pub fn labels(root: &Event, label_events: &[Event], maintainers: &[PublicKey]) -> Vec { let mut labels: Vec = root .tags .hashtags() .map(|hashtag| hashtag.to_string()) .collect(); for event in label_events { if !label_targets_root(event, root, maintainers) || !has_hashtag_labels(event) { continue; } for tag in event.tags.iter() { let slice = tag.as_slice(); if slice.len() >= 3 && slice[0] == "l" && slice[2] == "#t" && !slice[1].is_empty() { let label = &slice[1]; if !labels.contains(label) { labels.push(label.clone()); } } } } labels } /// The effective subject/title override of `root`, from authorized kind-1985 /// label events in the `#subject` namespace. Only the latest event wins /// (tiebreak: lexicographically larger event id, per NIP-01 replaceable /// semantics). Returns `None` when no valid override exists. pub fn subject_override( root: &Event, label_events: &[Event], maintainers: &[PublicKey], ) -> Option { label_events .iter() .filter(|event| label_targets_root(event, root, maintainers)) .filter(|event| { event .tags .iter() .any(|tag| tag.as_slice() == ["L", "#subject"]) && event.tags.iter().any(|tag| { let slice = tag.as_slice(); slice.len() >= 3 && slice[0] == "l" && slice[2] == "#subject" && !slice[1].is_empty() }) }) .max_by(|a, b| { a.created_at .cmp(&b.created_at) .then_with(|| a.id.to_string().cmp(&b.id.to_string())) }) .and_then(|event| { event.tags.iter().find_map(|tag| { let slice = tag.as_slice(); (slice.len() >= 3 && slice[0] == "l" && slice[2] == "#subject" && !slice[1].is_empty()) .then(|| slice[1].clone()) }) }) } /// The effective hashtag labels and subject override of `root` in one pass /// (mirrors ngit's `get_labels_and_subject`). pub fn labels_and_subject( root: &Event, label_events: &[Event], maintainers: &[PublicKey], ) -> (Vec, Option) { ( labels(root, label_events, maintainers), subject_override(root, label_events, maintainers), ) } /// The effective cover note of `root`: the latest authorized kind-1624 event /// (tiebreak: lexicographically larger event id, per NIP-01 replaceable /// semantics). Returns `None` when no valid cover note exists. pub fn cover_note<'a>( root: &Event, cover_notes: &'a [Event], maintainers: &[PublicKey], ) -> Option<&'a Event> { let root_id = root.id.to_hex(); cover_notes .iter() .filter(|event| { event.kind == COVER_NOTE_KIND && (event.pubkey == root.pubkey || maintainers.contains(&event.pubkey)) && event.tags.iter().any(|tag| { tag.kind() == "e" && tag.content().is_some_and(|content| content == root_id) }) }) .max_by(|a, b| { a.created_at .cmp(&b.created_at) .then_with(|| a.id.to_string().cmp(&b.id.to_string())) }) } #[cfg(test)] mod tests { use super::*; fn keys_from_hex(hex: &str) -> Keys { Keys::new(SecretKey::from_hex(hex).expect("valid secret key")) } fn signed(author: &Keys, kind: Kind, tags: Vec, created_at: u64) -> Event { EventBuilder::new(kind, "") .tags(tags) .custom_created_at(Timestamp::from(created_at)) .finalize(author) .expect("signed event") } fn root_event() -> Event { signed( &keys_from_hex("0000000000000000000000000000000000000000000000000000000000000001"), Kind::GitIssue, vec![Tag::hashtag("bug")], 100, ) } fn e_tag(event: &Event) -> Tag { Tag::parse(["e", &event.id.to_hex()]).expect("valid e tag") } #[test] fn labels_take_inline_hashtags_and_external_label_events() { let root = root_event(); let maintainer = keys_from_hex("0000000000000000000000000000000000000000000000000000000000000002"); let labels_event = signed( &maintainer, Kind::Label, vec![ e_tag(&root), Tag::parse(["L", "#t"]).expect("valid L tag"), Tag::parse(["l", "help-wanted", "#t"]).expect("valid l tag"), ], 200, ); let labels = labels(&root, &[labels_event], &[maintainer.public_key()]); assert_eq!(labels, vec!["bug", "help-wanted"]); } #[test] fn labels_ignore_unauthorized_and_misnamed_events() { let root = root_event(); let maintainer = keys_from_hex("0000000000000000000000000000000000000000000000000000000000000002"); let stranger = keys_from_hex("0000000000000000000000000000000000000000000000000000000000000003"); // A stranger's label event is not authorized. let stranger_labels = signed( &stranger, Kind::Label, vec![ e_tag(&root), Tag::parse(["L", "#t"]).expect("valid L tag"), Tag::parse(["l", "nope", "#t"]).expect("valid l tag"), ], 200, ); // A valid author referencing a different event. let other_labels = signed( &maintainer, Kind::Label, vec![ Tag::parse([ "e", "2222222222222222222222222222222222222222222222222222222222222222", ]) .expect("valid e tag"), Tag::parse(["L", "#t"]).expect("valid L tag"), Tag::parse(["l", "nope", "#t"]).expect("valid l tag"), ], 200, ); // A valid author without the namespace declaration. let missing_namespace = signed( &maintainer, Kind::Label, vec![ e_tag(&root), Tag::parse(["l", "nope", "#t"]).expect("valid l tag"), ], 200, ); assert_eq!( labels( &root, &[stranger_labels, other_labels, missing_namespace], &[maintainer.public_key()] ), vec!["bug"] ); } #[test] fn subject_override_latest_authorized_event_wins() { let root = root_event(); let maintainer = keys_from_hex("0000000000000000000000000000000000000000000000000000000000000002"); let older = signed( &maintainer, Kind::Label, vec![ e_tag(&root), Tag::parse(["L", "#subject"]).expect("valid L tag"), Tag::parse(["l", "Old title", "#subject"]).expect("valid l tag"), ], 200, ); let newer = signed( &maintainer, Kind::Label, vec![ e_tag(&root), Tag::parse(["L", "#subject"]).expect("valid L tag"), Tag::parse(["l", "New title", "#subject"]).expect("valid l tag"), ], 300, ); assert_eq!( subject_override(&root, &[newer, older], &[maintainer.public_key()]), Some("New title".to_owned()) ); } #[test] fn cover_note_latest_authorized_event_wins() { let root = root_event(); let maintainer = keys_from_hex("0000000000000000000000000000000000000000000000000000000000000002"); let stranger = keys_from_hex("0000000000000000000000000000000000000000000000000000000000000003"); let older = signed(&maintainer, COVER_NOTE_KIND, vec![e_tag(&root)], 200); let newer = signed(&maintainer, COVER_NOTE_KIND, vec![e_tag(&root)], 300); let unauthorized = signed(&stranger, COVER_NOTE_KIND, vec![e_tag(&root)], 400); let newer_id = newer.id; let events = [older, unauthorized, newer]; let maintainers = [maintainer.public_key()]; let note = cover_note(&root, &events, &maintainers); assert_eq!(note.map(|event| event.id), Some(newer_id)); } #[test] fn cover_note_none_without_valid_events() { let root = root_event(); assert_eq!(cover_note(&root, &[], &[]), None); } }