nostr_types/versioned/
metadata1.rs1use serde::de::{Deserialize, Deserializer, MapAccess, Visitor};
2use serde::ser::{Serialize, SerializeMap, Serializer};
3use serde_json::{json, Map, Value};
4use std::fmt;
5
6#[derive(Clone, Debug, Eq, PartialEq)]
13pub struct MetadataV1 {
14 pub name: Option<String>,
16
17 pub about: Option<String>,
19
20 pub picture: Option<String>,
22
23 pub nip05: Option<String>,
25
26 pub other: Map<String, Value>,
28}
29
30impl Default for MetadataV1 {
31 fn default() -> Self {
32 MetadataV1 {
33 name: None,
34 about: None,
35 picture: None,
36 nip05: None,
37 other: Map::new(),
38 }
39 }
40}
41
42impl MetadataV1 {
43 pub fn new() -> MetadataV1 {
45 MetadataV1::default()
46 }
47
48 #[allow(dead_code)]
49 pub(crate) fn mock() -> MetadataV1 {
50 let mut map = Map::new();
51 let _ = map.insert(
52 "display_name".to_string(),
53 Value::String("William Caserin".to_string()),
54 );
55 MetadataV1 {
56 name: Some("jb55".to_owned()),
57 about: None,
58 picture: None,
59 nip05: Some("jb55.com".to_owned()),
60 other: map,
61 }
62 }
63
64 pub fn lnurl(&self) -> Option<String> {
66 if let Some(Value::String(lud06)) = self.other.get("lud06") {
67 if let Ok(data) = bech32::decode(lud06) {
68 if data.0 == *crate::HRP_LNURL {
69 return Some(String::from_utf8_lossy(&data.1).to_string());
70 }
71 }
72 }
73
74 if let Some(Value::String(lud16)) = self.other.get("lud16") {
75 let vec: Vec<&str> = lud16.split('@').collect();
76 if vec.len() == 2 {
77 let user = &vec[0];
78 let domain = &vec[1];
79 return Some(format!("https://{domain}/.well-known/lnurlp/{user}"));
80 }
81 }
82
83 None
84 }
85}
86
87impl Serialize for MetadataV1 {
88 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
89 where
90 S: Serializer,
91 {
92 let mut map = serializer.serialize_map(Some(4 + self.other.len()))?;
93 map.serialize_entry("name", &json!(&self.name))?;
94 map.serialize_entry("about", &json!(&self.about))?;
95 map.serialize_entry("picture", &json!(&self.picture))?;
96 map.serialize_entry("nip05", &json!(&self.nip05))?;
97 for (k, v) in &self.other {
98 map.serialize_entry(&k, &v)?;
99 }
100 map.end()
101 }
102}
103
104impl<'de> Deserialize<'de> for MetadataV1 {
105 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
106 where
107 D: Deserializer<'de>,
108 {
109 deserializer.deserialize_map(MetadataV1Visitor)
110 }
111}
112
113struct MetadataV1Visitor;
114
115impl<'de> Visitor<'de> for MetadataV1Visitor {
116 type Value = MetadataV1;
117
118 fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
119 write!(f, "A JSON object")
120 }
121
122 fn visit_map<M>(self, mut access: M) -> Result<MetadataV1, M::Error>
123 where
124 M: MapAccess<'de>,
125 {
126 let mut map: Map<String, Value> = Map::new();
127 while let Some((key, value)) = access.next_entry::<String, Value>()? {
128 let _ = map.insert(key, value);
129 }
130
131 let mut m: MetadataV1 = Default::default();
132
133 if let Some(Value::String(s)) = map.remove("name") {
134 m.name = Some(s);
135 }
136 if let Some(Value::String(s)) = map.remove("about") {
137 m.about = Some(s);
138 }
139 if let Some(Value::String(s)) = map.remove("picture") {
140 m.picture = Some(s);
141 }
142 if let Some(Value::String(s)) = map.remove("nip05") {
143 m.nip05 = Some(s);
144 }
145
146 m.other = map;
147
148 Ok(m)
149 }
150}
151
152#[cfg(test)]
153mod test {
154 use super::*;
155
156 test_serde! {MetadataV1, test_metadata_serde}
157
158 #[test]
159 fn test_metadata_print_json() {
160 let m = MetadataV1::mock();
162 println!("{}", serde_json::to_string(&m).unwrap());
163 }
164
165 #[test]
166 fn test_tolerate_nulls() {
167 let json = r##"{"name":"monlovesmango","picture":"https://astral.ninja/aura/monlovesmango.svg","about":"building on nostr","nip05":"monlovesmango@astral.ninja","lud06":null,"testing":"123"}"##;
168 let m: MetadataV1 = serde_json::from_str(json).unwrap();
169 assert_eq!(m.name, Some("monlovesmango".to_owned()));
170 assert_eq!(m.other.get("lud06"), Some(&Value::Null));
171 assert_eq!(
172 m.other.get("testing"),
173 Some(&Value::String("123".to_owned()))
174 );
175 }
176
177 #[test]
178 fn test_metadata_lnurls() {
179 let json = r##"{"name":"mikedilger","about":"Author of Gossip client: https://github.com/mikedilger/gossip\nexpat American living in New Zealand","picture":"https://avatars.githubusercontent.com/u/1669069","nip05":"_@mikedilger.com","banner":"https://mikedilger.com/banner.jpg","display_name":"Michael Dilger","location":"New Zealand","lud06":"lnurl1dp68gurn8ghj7ampd3kx2ar0veekzar0wd5xjtnrdakj7tnhv4kxctttdehhwm30d3h82unvwqhkgetrv4h8gcn4dccnxv563ep","website":"https://mikedilger.com"}"##;
181 let m: MetadataV1 = serde_json::from_str(json).unwrap();
182 assert_eq!(
183 m.lnurl().as_deref(),
184 Some("https://walletofsatoshi.com/.well-known/lnurlp/decentbun13")
185 );
186
187 let json = r##"{"name":"mikedilger","about":"Author of Gossip client: https://github.com/mikedilger/gossip\nexpat American living in New Zealand","picture":"https://avatars.githubusercontent.com/u/1669069","nip05":"_@mikedilger.com","banner":"https://mikedilger.com/banner.jpg","display_name":"Michael Dilger","location":"New Zealand","lud16":"decentbun13@walletofsatoshi.com","website":"https://mikedilger.com"}"##;
189 let m: MetadataV1 = serde_json::from_str(json).unwrap();
190 assert_eq!(
191 m.lnurl().as_deref(),
192 Some("https://walletofsatoshi.com/.well-known/lnurlp/decentbun13")
193 );
194 }
195}