1use nmp_core::substrate::KernelEvent;
9
10use crate::{ParentResolver, ThreadPointer};
11
12#[derive(Clone, Copy, Debug, Default)]
14pub struct EtagThreadResolver;
15
16impl ParentResolver for EtagThreadResolver {
17 fn parent(&self, event: &KernelEvent) -> Option<ThreadPointer> {
18 parse_e_refs(&event.tags)
19 .reply
20 .map(|r| ThreadPointer::Event {
21 id: r.id,
22 relay: r.relay,
23 kind: None,
24 })
25 }
26
27 fn root(&self, event: &KernelEvent) -> Option<ThreadPointer> {
28 parse_e_refs(&event.tags)
29 .root
30 .map(|r| ThreadPointer::Event {
31 id: r.id,
32 relay: r.relay,
33 kind: None,
34 })
35 }
36
37 fn parent_author(&self, event: &KernelEvent) -> Option<String> {
38 event
39 .tags
40 .iter()
41 .find(|tag| tag.first().map(String::as_str) == Some("p"))
42 .and_then(|tag| tag.get(1).cloned())
43 }
44}
45
46#[derive(Clone)]
47struct ERef {
48 id: String,
49 relay: Option<String>,
50 marker: Option<String>,
51}
52
53#[derive(Default)]
54struct ERefs {
55 root: Option<ERef>,
56 reply: Option<ERef>,
57}
58
59fn parse_e_refs(tags: &[Vec<String>]) -> ERefs {
60 let e_tags: Vec<&Vec<String>> = tags
61 .iter()
62 .filter(|tag| tag.first().map(String::as_str) == Some("e"))
63 .collect();
64 let has_marker = e_tags.iter().any(|tag| {
65 matches!(
66 tag.get(3).map(String::as_str),
67 Some("root" | "reply" | "mention")
68 )
69 });
70
71 if has_marker {
72 let mut refs = ERefs::default();
73 for tag in e_tags {
74 let Some(eref) = e_ref(tag) else { continue };
75 match eref.marker.as_deref() {
76 Some("root") if refs.root.is_none() => refs.root = Some(eref),
77 Some("reply") if refs.reply.is_none() => refs.reply = Some(eref),
78 _ => {}
79 }
80 }
81 if refs.reply.is_none() {
82 refs.reply = refs.root.clone();
83 }
84 return refs;
85 }
86
87 let resolved: Vec<ERef> = e_tags.into_iter().filter_map(|tag| e_ref(tag)).collect();
88 match resolved.len() {
89 0 => ERefs::default(),
90 1 => ERefs {
91 root: Some(resolved[0].clone()),
92 reply: Some(resolved[0].clone()),
93 },
94 n => ERefs {
95 root: Some(resolved[0].clone()),
96 reply: Some(resolved[n - 1].clone()),
97 },
98 }
99}
100
101fn e_ref(tag: &[String]) -> Option<ERef> {
102 let id = tag.get(1)?.clone();
103 if id.is_empty() {
104 return None;
105 }
106 Some(ERef {
107 id,
108 relay: tag.get(2).filter(|s| !s.is_empty()).cloned(),
109 marker: tag.get(3).filter(|s| !s.is_empty()).cloned(),
110 })
111}