block.rs (15347B)
1 use crate::{bindings, Note, Transaction}; 2 3 #[derive(Debug)] 4 pub struct Blocks<'a> { 5 ptr: *mut bindings::ndb_blocks, 6 txn: Option<&'a Transaction>, 7 } 8 9 #[derive(Debug)] 10 pub struct Block<'a> { 11 ptr: *mut bindings::ndb_block, 12 13 #[allow(dead_code)] 14 txn: Option<&'a Transaction>, 15 } 16 17 pub struct BlockIter<'a> { 18 iter: bindings::ndb_block_iterator, 19 txn: Option<&'a Transaction>, 20 } 21 22 #[derive(Debug, Eq, PartialEq)] 23 pub enum BlockType { 24 Hashtag, 25 Text, 26 MentionIndex, 27 MentionBech32, 28 Url, 29 Invoice, 30 } 31 32 #[derive(Debug, Eq, PartialEq)] 33 pub enum Bech32Type { 34 Event, 35 Pubkey, 36 Profile, 37 Note, 38 Relay, 39 Addr, 40 Secret, 41 } 42 43 pub enum Mention<'a> { 44 Pubkey(&'a bindings::bech32_npub), 45 Event(&'a bindings::bech32_nevent), 46 Profile(&'a bindings::bech32_nprofile), 47 Note(&'a bindings::bech32_note), 48 Relay(&'a bindings::bech32_nrelay), 49 Secret(&'a bindings::bech32_nsec), 50 Addr(&'a bindings::bech32_naddr), 51 } 52 53 impl bindings::ndb_str_block { 54 pub fn as_str(&self) -> &str { 55 unsafe { 56 let ptr = bindings::ndb_str_block_ptr(self as *const Self as *mut Self) as *const u8; 57 let len = bindings::ndb_str_block_len(self as *const Self as *mut Self); 58 let byte_slice = std::slice::from_raw_parts(ptr, len.try_into().unwrap()); 59 std::str::from_utf8_unchecked(byte_slice) 60 } 61 } 62 } 63 64 impl bindings::bech32_nrelay { 65 pub fn as_str(&self) -> &str { 66 self.relay.as_str() 67 } 68 } 69 70 impl bindings::bech32_nprofile { 71 pub fn pubkey(&self) -> &[u8; 32] { 72 unsafe { &*(self.pubkey as *const [u8; 32]) } 73 } 74 75 pub fn relays_iter(&self) -> impl Iterator<Item = &str> { 76 self.relays.relays[0..(self.relays.num_relays as usize)] 77 .iter() 78 .map(|block| block.as_str()) 79 } 80 } 81 82 impl bindings::bech32_npub { 83 pub fn pubkey(&self) -> &[u8; 32] { 84 unsafe { &*(self.pubkey as *const [u8; 32]) } 85 } 86 } 87 88 impl bindings::bech32_note { 89 pub fn id(&self) -> &[u8; 32] { 90 unsafe { &*(self.event_id as *const [u8; 32]) } 91 } 92 } 93 94 impl bindings::bech32_nevent { 95 pub fn id(&self) -> &[u8; 32] { 96 unsafe { &*(self.event_id as *const [u8; 32]) } 97 } 98 99 pub fn pubkey(&self) -> Option<&[u8; 32]> { 100 unsafe { 101 if self.pubkey.is_null() { 102 return None; 103 } 104 Some(&*(self.pubkey as *const [u8; 32])) 105 } 106 } 107 108 pub fn relays_iter(&self) -> impl Iterator<Item = &str> { 109 self.relays.relays[0..(self.relays.num_relays as usize)] 110 .iter() 111 .map(|block| block.as_str()) 112 } 113 } 114 115 impl bindings::bech32_naddr { 116 pub fn relays_iter(&self) -> impl Iterator<Item = &str> { 117 self.relays.relays[0..(self.relays.num_relays as usize)] 118 .iter() 119 .map(|block| block.as_str()) 120 } 121 } 122 123 impl<'a> Mention<'a> { 124 pub fn new(bech32: &'a bindings::nostr_bech32) -> Self { 125 unsafe { 126 match Bech32Type::from_ctype(bech32.type_) { 127 Bech32Type::Event => Mention::Event(&bech32.__bindgen_anon_1.nevent), 128 Bech32Type::Pubkey => Mention::Pubkey(&bech32.__bindgen_anon_1.npub), 129 Bech32Type::Profile => Mention::Profile(&bech32.__bindgen_anon_1.nprofile), 130 Bech32Type::Note => Mention::Note(&bech32.__bindgen_anon_1.note), 131 Bech32Type::Relay => Mention::Relay(&bech32.__bindgen_anon_1.nrelay), 132 Bech32Type::Secret => Mention::Secret(&bech32.__bindgen_anon_1.nsec), 133 Bech32Type::Addr => Mention::Addr(&bech32.__bindgen_anon_1.naddr), 134 } 135 } 136 } 137 } 138 139 impl Bech32Type { 140 pub(crate) fn from_ctype(typ: bindings::nostr_bech32_type) -> Bech32Type { 141 match typ { 142 1 => Bech32Type::Note, 143 2 => Bech32Type::Pubkey, 144 3 => Bech32Type::Profile, 145 4 => Bech32Type::Event, 146 5 => Bech32Type::Relay, 147 6 => Bech32Type::Addr, 148 7 => Bech32Type::Secret, 149 _ => panic!("Invalid bech32 type"), 150 } 151 } 152 } 153 154 impl<'a> Block<'a> { 155 #[allow(dead_code)] 156 pub(crate) fn new_transactional( 157 ptr: *mut bindings::ndb_block, 158 txn: &'a Transaction, 159 ) -> Block<'a> { 160 Block { 161 ptr, 162 txn: Some(txn), 163 } 164 } 165 166 #[allow(dead_code)] 167 pub(crate) fn new_owned(ptr: *mut bindings::ndb_block) -> Block<'static> { 168 Block { ptr, txn: None } 169 } 170 171 pub(crate) fn new(ptr: *mut bindings::ndb_block, txn: Option<&'a Transaction>) -> Block<'a> { 172 Block { ptr, txn } 173 } 174 175 pub fn as_ptr(&self) -> *mut bindings::ndb_block { 176 self.ptr 177 } 178 179 pub fn as_mention(&self) -> Option<Mention<'a>> { 180 if self.blocktype() != BlockType::MentionBech32 { 181 return None; 182 } 183 Some(Mention::new(self.c_bech32())) 184 } 185 186 pub fn as_str(&self) -> &'a str { 187 unsafe { 188 let str_block = bindings::ndb_block_str(self.as_ptr()); 189 if str_block.is_null() { 190 return ""; 191 } 192 193 (*str_block).as_str() 194 } 195 } 196 197 fn c_bech32(&self) -> &'a bindings::nostr_bech32 { 198 unsafe { &(*self.as_ptr()).block.mention_bech32.bech32 } 199 } 200 201 pub fn blocktype(&self) -> BlockType { 202 let typ = unsafe { bindings::ndb_get_block_type(self.as_ptr()) }; 203 match typ { 204 1 => BlockType::Hashtag, 205 2 => BlockType::Text, 206 3 => BlockType::MentionIndex, 207 4 => BlockType::MentionBech32, 208 5 => BlockType::Url, 209 6 => BlockType::Invoice, 210 _ => panic!("Invalid blocktype {}", typ), 211 } 212 } 213 } 214 215 impl<'a> Blocks<'a> { 216 pub(crate) fn new_transactional( 217 ptr: *mut bindings::ndb_blocks, 218 txn: &'a Transaction, 219 ) -> Blocks<'a> { 220 Blocks { 221 ptr, 222 txn: Some(txn), 223 } 224 } 225 226 #[allow(dead_code)] 227 pub(crate) fn new_owned(ptr: *mut bindings::ndb_blocks) -> Blocks<'static> { 228 Blocks { ptr, txn: None } 229 } 230 231 pub fn iter(&self, note: &Note<'a>) -> BlockIter<'a> { 232 let content = note.content_ptr(); 233 match self.txn { 234 Some(txn) => BlockIter::new_transactional(content, self.as_ptr(), txn), 235 None => BlockIter::new_owned(content, self.as_ptr()), 236 } 237 } 238 239 pub fn as_ptr(&self) -> *mut bindings::ndb_blocks { 240 self.ptr 241 } 242 } 243 244 impl Drop for Blocks<'_> { 245 fn drop(&mut self) { 246 unsafe { bindings::ndb_blocks_free(self.as_ptr()) }; 247 } 248 } 249 250 impl<'a> BlockIter<'a> { 251 pub(crate) fn new_transactional( 252 content: *const ::std::os::raw::c_char, 253 blocks: *mut bindings::ndb_blocks, 254 txn: &'a Transaction, 255 ) -> BlockIter<'a> { 256 let type_ = bindings::ndb_block_type_BLOCK_TEXT; 257 let mention_index: u32 = 1; 258 let block = bindings::ndb_block__bindgen_ty_1 { mention_index }; 259 let block = bindings::ndb_block { type_, block }; 260 let p = blocks as *mut ::std::os::raw::c_uchar; 261 let iter = bindings::ndb_block_iterator { 262 content, 263 blocks, 264 p, 265 block, 266 }; 267 let mut block_iter = BlockIter { 268 iter, 269 txn: Some(txn), 270 }; 271 unsafe { bindings::ndb_blocks_iterate_start(content, blocks, &mut block_iter.iter) }; 272 block_iter 273 } 274 275 pub(crate) fn new_owned( 276 content: *const ::std::os::raw::c_char, 277 blocks: *mut bindings::ndb_blocks, 278 ) -> BlockIter<'static> { 279 let type_ = bindings::ndb_block_type_BLOCK_TEXT; 280 let mention_index: u32 = 1; 281 let block = bindings::ndb_block__bindgen_ty_1 { mention_index }; 282 let block = bindings::ndb_block { type_, block }; 283 let p = blocks as *mut ::std::os::raw::c_uchar; 284 let mut iter = bindings::ndb_block_iterator { 285 content, 286 blocks, 287 p, 288 block, 289 }; 290 unsafe { bindings::ndb_blocks_iterate_start(content, blocks, &mut iter) }; 291 BlockIter { iter, txn: None } 292 } 293 294 pub fn as_ptr(&self) -> *const bindings::ndb_block_iterator { 295 &self.iter 296 } 297 298 pub fn as_mut_ptr(&self) -> *mut bindings::ndb_block_iterator { 299 self.as_ptr() as *mut bindings::ndb_block_iterator 300 } 301 } 302 303 impl<'a> Iterator for BlockIter<'a> { 304 type Item = Block<'a>; 305 306 fn next(&mut self) -> Option<Self::Item> { 307 let block = unsafe { bindings::ndb_blocks_iterate_next(self.as_mut_ptr()) }; 308 if block.is_null() { 309 return None; 310 } 311 312 Some(Block::new(block, self.txn)) 313 } 314 } 315 316 /* 317 impl<'a> IntoIterator for Blocks<'a> { 318 type Item = Block<'a>; 319 type IntoIter = BlockIter<'a>; 320 321 fn into_iter(self) -> Self::IntoIter { 322 match self.txn { 323 Some(txn) => BlockIter::new_transactional(self.as_ptr(), txn), 324 None => BlockIter::new_owned(self.as_ptr()), 325 } 326 } 327 } 328 */ 329 330 #[cfg(test)] 331 mod tests { 332 use super::*; 333 use crate::test_util; 334 use crate::{Config, Ndb}; 335 336 #[test] 337 fn note_blocks_work() { 338 let db = "target/testdbs/note_blocks"; 339 test_util::cleanup_db(&db); 340 341 { 342 let ndb = Ndb::new(db, &Config::new()).expect("ndb"); 343 ndb.process_event("[\"EVENT\",\"s\",{\"id\":\"d28ac02e277c3cf2744b562a414fd92d5fea554a737901364735bfe74577f304\",\"pubkey\":\"b5b1b5d2914daa2eda99af22ae828effe98730bf69dcca000fa37bfb9e395e32\",\"created_at\": 1703989205,\"kind\": 1,\"tags\": [],\"content\": \"#hashtags, are neat nostr:nprofile1qqsr9cvzwc652r4m83d86ykplrnm9dg5gwdvzzn8ameanlvut35wy3gpz3mhxue69uhhyetvv9ujuerpd46hxtnfduyu75sw https://github.com/damus-io\",\"sig\": \"07af3062616a17ef392769cadb170ac855c817c103e007c72374499bbadb2fe8917a0cc5b3fdc5aa5d56de086e128b3aeaa8868f6fe42a409767241b6a29cc94\"}]").expect("process ok"); 344 } 345 346 { 347 let ndb = Ndb::new(db, &Config::new()).expect("ndb"); 348 let id = 349 hex::decode("d28ac02e277c3cf2744b562a414fd92d5fea554a737901364735bfe74577f304") 350 .expect("hex id"); 351 let pubkey = 352 hex::decode("32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245") 353 .expect("jb55 pubkey"); 354 let txn = Transaction::new(&ndb).expect("txn"); 355 let id_bytes: [u8; 32] = id.try_into().expect("id bytes"); 356 let pubkey_bytes: [u8; 32] = pubkey.try_into().expect("pubkey bytes"); 357 let note = ndb.get_note_by_id(&txn, &id_bytes).unwrap(); 358 let blocks = ndb 359 .get_blocks_by_key(&txn, note.key().unwrap()) 360 .expect("note"); 361 let mut c = 0; 362 for block in blocks.iter(¬e) { 363 match c { 364 0 => { 365 assert_eq!(block.blocktype(), BlockType::Hashtag); 366 assert_eq!(block.as_str(), "hashtags"); 367 } 368 369 1 => { 370 assert_eq!(block.blocktype(), BlockType::Text); 371 assert_eq!(block.as_str(), ", are neat "); 372 } 373 374 2 => { 375 assert_eq!(block.blocktype(), BlockType::MentionBech32); 376 assert_eq!(block.as_str(), "nprofile1qqsr9cvzwc652r4m83d86ykplrnm9dg5gwdvzzn8ameanlvut35wy3gpz3mhxue69uhhyetvv9ujuerpd46hxtnfduyu75sw"); 377 match block.as_mention().unwrap() { 378 Mention::Profile(p) => assert_eq!(p.pubkey(), &pubkey_bytes), 379 _ => assert!(false), 380 }; 381 } 382 383 3 => { 384 assert_eq!(block.blocktype(), BlockType::Text); 385 assert_eq!(block.as_str(), " "); 386 } 387 388 4 => { 389 assert_eq!(block.blocktype(), BlockType::Url); 390 assert_eq!(block.as_str(), "https://github.com/damus-io"); 391 } 392 393 _ => assert!(false), 394 } 395 396 c += 1; 397 } 398 } 399 400 test_util::cleanup_db(&db); 401 } 402 403 #[test] 404 fn nprofile_relays_work() { 405 let db = "target/testdbs/nprofile_relays"; 406 test_util::cleanup_db(&db); 407 408 { 409 let ndb = Ndb::new(db, &Config::new()).expect("ndb"); 410 ndb.process_event("[\"EVENT\",\"s\",{\"kind\":1,\"id\":\"06449981af8f0e00503ea88deb6c2d3e9d65e1e4f032744d9ca4949dce1ce296\",\"pubkey\":\"850605096dbfb50b929e38a6c26c3d56c425325c85e05de29b759bc0e5d6cebc\",\"created_at\":1737747111,\"tags\":[[\"p\",\"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d\"]],\"content\":\"This sample event has the mention example from NIP-19: nostr:nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpp4mhxue69uhhytnc9e3k7mgpz4mhxue69uhkg6nzv9ejuumpv34kytnrdaksjlyr9p\nPls ignore ...\",\"sig\":\"26a4d8f51af0ca7f6c7140ba2487ed4a99edc03e95692e01e636917417dac78989e735559a3a05e1805f242aa634c545fd4d820b962c19301faa55b05bea5da7\"}]").expect("process ok"); 411 } 412 { 413 let ndb = Ndb::new(db, &Config::new()).expect("ndb"); 414 let id = 415 hex::decode("06449981af8f0e00503ea88deb6c2d3e9d65e1e4f032744d9ca4949dce1ce296") 416 .expect("hex id"); 417 let pubkey = 418 hex::decode("3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d") 419 .expect("fiatjaf pubkey"); 420 let txn = Transaction::new(&ndb).expect("txn"); 421 let id_bytes: [u8; 32] = id.try_into().expect("id bytes"); 422 let pubkey_bytes: [u8; 32] = pubkey.try_into().expect("pubkey bytes"); 423 424 let note = ndb.get_note_by_id(&txn, &id_bytes).unwrap(); 425 let blocks = ndb 426 .get_blocks_by_key(&txn, note.key().unwrap()) 427 .expect("note"); 428 429 let mut nprofile_check = false; 430 431 for (ndx, block) in blocks.iter(¬e).enumerate() { 432 println!("block {}: {:?}: {:?}", ndx, block.blocktype(), &block); 433 if block.blocktype() == BlockType::MentionBech32 { 434 // https://github.com/nostr-protocol/nips/blob/master/19.md#examples 435 assert_eq!( 436 block.as_str(), "nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpp4mhxue69uhhytnc9e3k7mgpz4mhxue69uhkg6nzv9ejuumpv34kytnrdaksjlyr9p" 437 ); 438 439 if let Mention::Profile(p) = block.as_mention().unwrap() { 440 assert_eq!(p.pubkey(), &pubkey_bytes); 441 assert_eq!(p.relays.num_relays, 2); 442 assert_eq!(p.relays.relays[0].as_str(), "wss://r.x.com"); 443 assert_eq!(p.relays.relays[1].as_str(), "wss://djbas.sadkb.com"); 444 let relays: Vec<&str> = p.relays_iter().collect(); 445 assert_eq!(relays, vec!["wss://r.x.com", "wss://djbas.sadkb.com"]); 446 nprofile_check = true; 447 } 448 } 449 } 450 assert!(nprofile_check, "Expected nprofile block was not found"); 451 } 452 453 test_util::cleanup_db(&db); 454 } 455 }