1#![deny(
8 missing_debug_implementations,
9 trivial_casts,
10 trivial_numeric_casts,
11 unused_import_braces,
12 unused_results,
14 unused_lifetimes,
15 unused_labels,
16 unused_extern_crates,
17 non_ascii_idents,
18 keyword_idents,
19 deprecated_in_future,
20 unstable_features,
21 single_use_lifetimes,
22 unreachable_pub,
24 missing_docs,
25 missing_copy_implementations
26)]
27#![deny(clippy::string_slice)]
28
29mod error;
30pub use error::Error;
31
32#[cfg(test)]
33macro_rules! test_serde {
34 ($t:ty, $fnname:ident) => {
35 #[test]
36 fn $fnname() {
37 let a = <$t>::mock();
38 let x = serde_json::to_string(&a).unwrap();
39 println!("{}", x);
40 let b = serde_json::from_str(&x).unwrap();
41 assert_eq!(a, b);
42 }
43 };
44}
45
46#[cfg(test)]
47macro_rules! test_serde_async {
48 ($t:ty, $fnname:ident) => {
49 #[tokio::test]
50 async fn $fnname() {
51 let a = <$t>::mock().await;
52 let x = serde_json::to_string(&a).unwrap();
53 println!("{}", x);
54 let b = serde_json::from_str(&x).unwrap();
55 assert_eq!(a, b);
56 }
57 };
58}
59
60#[cfg(test)]
61macro_rules! test_serde_val {
62 ($fnname:ident, $val:expr) => {
63 #[test]
64 fn $fnname() {
65 let a = $val;
66 let x = serde_json::to_string(&a).unwrap();
67 println!("{}", x);
68 let b = serde_json::from_str(&x).unwrap();
69 assert_eq!(a, b);
70 }
71 };
72}
73
74#[cfg(test)]
75macro_rules! test_serde_val_async {
76 ($fnname:ident, $val:expr) => {
77 #[tokio::test]
78 async fn $fnname() {
79 let a = $val;
80 let x = serde_json::to_string(&a).unwrap();
81 println!("{}", x);
82 let b = serde_json::from_str(&x).unwrap();
83 assert_eq!(a, b);
84 }
85 };
86}
87
88#[cfg(feature = "client")]
90pub mod client;
91
92#[cfg(feature = "nip46")]
94pub mod nip46;
95
96mod types;
97pub use types::{
98 find_nostr_bech32_pos, find_nostr_url_pos, ClientMessage, ContentEncryptionAlgorithm,
99 ContentSegment, CountResult, DelegationConditions, EncryptedPrivateKey, Event, EventDelegation,
100 EventKind, EventKindIterator, EventKindOrRange, EventReference, ExportableSigner, Fee,
101 FileMetadata, Filter, Hll8, Id, IdHex, Identity, KeySecurity, KeySigner, LockableSigner,
102 Metadata, MilliSatoshi, MutExportableSigner, NAddr, NEvent, Nip05, NostrBech32, NostrUrl,
103 ParsedTag, PayRequestData, PreEvent, PrivateKey, Profile, PublicKey, PublicKeyHex, RelayFees,
104 RelayInformationDocument, RelayLimitation, RelayList, RelayListUsage, RelayMessage,
105 RelayOrigin, RelayRetention, RelayUrl, RelayUsage, RelayUsageSet, Rumor, ShatteredContent,
106 Signature, SignatureHex, Signer, SignerExt, SimpleRelayList, SimpleRelayUsage, Span,
107 SubscriptionId, Tag, UncheckedUrl, Unixtime, Url, Why, XOnlyPublicKey, ZapData,
108};
109
110mod versioned;
111pub use versioned::{
112 EventV3, FeeV1, FilterV1, FilterV2, MetadataV1, MetadataV2, Nip05V1, PreEventV3, RelayFeesV1,
113 RelayInformationDocumentV1, RelayInformationDocumentV2, RelayLimitationV1, RelayLimitationV2,
114 RelayRetentionV1, RumorV3, TagV3, ZapDataV1, ZapDataV2,
115};
116
117#[inline]
118pub(crate) fn get_leading_zero_bits(bytes: &[u8]) -> u8 {
119 let mut res = 0_u8;
120 for b in bytes {
121 if *b == 0 {
122 res += 8;
123 } else {
124 res += b.leading_zeros() as u8;
125 return res;
126 }
127 }
128 res
129}
130
131trait IntoVec<T> {
132 fn into_vec(self) -> Vec<T>;
133}
134
135impl<T> IntoVec<T> for Option<T> {
136 fn into_vec(self) -> Vec<T> {
137 match self {
138 None => vec![],
139 Some(t) => vec![t],
140 }
141 }
142}
143
144use bech32::Hrp;
145lazy_static::lazy_static! {
146 static ref HRP_LNURL: Hrp = Hrp::parse("lnurl").expect("HRP error on lnurl");
147 static ref HRP_NADDR: Hrp = Hrp::parse("naddr").expect("HRP error on naddr");
148 static ref HRP_NCRYPTSEC: Hrp = Hrp::parse("ncryptsec").expect("HRP error on ncryptsec");
149 static ref HRP_NEVENT: Hrp = Hrp::parse("nevent").expect("HRP error on nevent");
150 static ref HRP_NOTE: Hrp = Hrp::parse("note").expect("HRP error on note");
151 static ref HRP_NPROFILE: Hrp = Hrp::parse("nprofile").expect("HRP error on nprofile");
152 static ref HRP_NPUB: Hrp = Hrp::parse("npub").expect("HRP error on npub");
153 static ref HRP_NRELAY: Hrp = Hrp::parse("nrelay").expect("HRP error on nrelay");
154 static ref HRP_NSEC: Hrp = Hrp::parse("nsec").expect("HRP error on nsec");
155}
156
157pub fn add_pubkey_to_tags(
159 existing_tags: &mut Vec<Tag>,
160 new_pubkey: PublicKey,
161 new_hint: Option<UncheckedUrl>,
162) -> usize {
163 let index = existing_tags.iter().position(|existing_tag| {
164 if let Ok(ParsedTag::Pubkey { pubkey, .. }) = existing_tag.parse() {
165 pubkey == new_pubkey
166 } else {
167 false
168 }
169 });
170
171 if let Some(idx) = index {
172 existing_tags[idx].set_index(
174 2,
175 match new_hint {
176 Some(u) => u.as_str().to_owned(),
177 None => "".to_owned(),
178 },
179 );
180 existing_tags[idx].trim();
181 idx
182 } else {
183 existing_tags.push(
184 ParsedTag::Pubkey {
185 pubkey: new_pubkey,
186 recommended_relay_url: new_hint,
187 petname: None,
188 }
189 .into_tag(),
190 );
191 existing_tags.len() - 1
192 }
193}
194
195pub fn add_event_to_tags(
197 existing_tags: &mut Vec<Tag>,
198 new_id: Id,
199 new_hint: Option<UncheckedUrl>,
200 new_marker: &str,
201 new_pubkey: Option<PublicKey>,
202 use_quote: bool,
203) -> usize {
204 if new_marker == "mention" && use_quote {
206 let index = existing_tags.iter().position(|existing_tag| {
207 if let Ok(ParsedTag::Quote { id, .. }) = existing_tag.parse() {
208 id == new_id
209 } else {
210 false
211 }
212 });
213
214 if let Some(idx) = index {
215 existing_tags[idx].set_index(
217 2,
218 match new_hint {
219 Some(u) => u.as_str().to_owned(),
220 None => "".to_owned(),
221 },
222 );
223 existing_tags[idx].set_index(
224 3,
225 match new_pubkey {
226 Some(pk) => pk.as_hex_string(),
227 None => "".to_owned(),
228 },
229 );
230 existing_tags[idx].trim();
231 idx
232 } else {
233 let newtag = ParsedTag::Quote {
234 id: new_id,
235 recommended_relay_url: new_hint,
236 author_pubkey: new_pubkey,
237 }
238 .into_tag();
239 existing_tags.push(newtag);
240 existing_tags.len() - 1
241 }
242 } else {
243 let index = existing_tags.iter().position(|existing_tag| {
244 if let Ok(ParsedTag::Event { id, .. }) = existing_tag.parse() {
245 id == new_id
246 } else {
247 false
248 }
249 });
250
251 if let Some(idx) = index {
252 existing_tags[idx].set_index(
254 2,
255 match new_hint {
256 Some(u) => u.as_str().to_owned(),
257 None => "".to_owned(),
258 },
259 );
260 existing_tags[idx].set_index(3, new_marker.to_owned());
261 existing_tags[idx].set_index(
262 4,
263 match new_pubkey {
264 Some(pk) => pk.as_hex_string(),
265 None => "".to_owned(),
266 },
267 );
268 existing_tags[idx].trim();
269 idx
270 } else {
271 let newtag = ParsedTag::Event {
272 id: new_id,
273 recommended_relay_url: new_hint,
274 marker: Some(new_marker.to_string()),
275 author_pubkey: new_pubkey,
276 }
277 .into_tag();
278 existing_tags.push(newtag);
279 existing_tags.len() - 1
280 }
281 }
282}
283
284pub fn add_addr_to_tags(
286 existing_tags: &mut Vec<Tag>,
287 new_addr: &NAddr,
288 new_marker: Option<String>,
289) -> usize {
290 let index = existing_tags.iter().position(|existing_tag| {
291 if let Ok(ParsedTag::Address { address, .. }) = existing_tag.parse() {
292 address.kind == new_addr.kind
293 && address.author == new_addr.author
294 && address.d == new_addr.d
295 } else {
296 false
297 }
298 });
299
300 if let Some(idx) = index {
301 existing_tags[idx].set_index(
303 2,
304 match new_marker {
305 Some(s) => s,
306 None => "".to_owned(),
307 },
308 );
309 existing_tags[idx].trim();
310 idx
311 } else {
312 existing_tags.push(
313 ParsedTag::Address {
314 address: new_addr.clone(),
315 marker: new_marker,
316 }
317 .into_tag(),
318 );
319 existing_tags.len() - 1
320 }
321}
322
323pub fn add_subject_to_tags_if_missing(existing_tags: &mut Vec<Tag>, subject: String) {
325 if !existing_tags.iter().any(|t| t.tagname() == "subject") {
326 existing_tags.push(ParsedTag::Subject(subject).into_tag());
327 }
328}