html.rs (5190B)
1 use crate::Error; 2 use crate::{ 3 abbrev::{abbrev_str, abbreviate}, 4 render, Notecrumbs, 5 }; 6 use html_escape; 7 use http_body_util::Full; 8 use hyper::{ 9 body::Bytes, header, server::conn::http1, service::service_fn, Request, Response, StatusCode, 10 }; 11 use hyper_util::rt::TokioIo; 12 use log::error; 13 use nostr_sdk::prelude::{Nip19, ToBech32}; 14 use nostrdb::{BlockType, Blocks, Mention, Ndb, Note, Transaction}; 15 use std::io::Write; 16 17 pub fn render_note_content(body: &mut Vec<u8>, ndb: &Ndb, note: &Note, blocks: &Blocks) { 18 for block in blocks.iter(note) { 19 let blocktype = block.blocktype(); 20 21 match block.blocktype() { 22 BlockType::Url => { 23 let url = html_escape::encode_text(block.as_str()); 24 write!(body, r#"<a href="{}">{}</a>"#, url, url); 25 } 26 27 BlockType::Hashtag => { 28 let hashtag = html_escape::encode_text(block.as_str()); 29 write!(body, r#"<span class="hashtag">#{}</span>"#, hashtag); 30 } 31 32 BlockType::Text => { 33 let text = html_escape::encode_text(block.as_str()); 34 write!(body, r"{}", text); 35 } 36 37 BlockType::Invoice => { 38 write!(body, r"{}", block.as_str()); 39 } 40 41 BlockType::MentionIndex => { 42 write!(body, r"@nostrich"); 43 } 44 45 BlockType::MentionBech32 => { 46 let pk = match block.as_mention().unwrap() { 47 Mention::Event(_) 48 | Mention::Note(_) 49 | Mention::Profile(_) 50 | Mention::Pubkey(_) 51 | Mention::Secret(_) 52 | Mention::Addr(_) => { 53 write!( 54 body, 55 r#"<a href="/{}">@{}</a>"#, 56 block.as_str(), 57 &abbrev_str(block.as_str()) 58 ); 59 } 60 61 Mention::Relay(relay) => { 62 write!( 63 body, 64 r#"<a href="/{}">{}</a>"#, 65 block.as_str(), 66 &abbrev_str(relay.as_str()) 67 ); 68 } 69 }; 70 } 71 }; 72 } 73 } 74 75 pub fn serve_note_html( 76 app: &Notecrumbs, 77 nip19: &Nip19, 78 note_data: &render::NoteRenderData, 79 r: Request<hyper::body::Incoming>, 80 ) -> Result<Response<Full<Bytes>>, Error> { 81 let mut data = Vec::new(); 82 83 // indices 84 // 85 // 0: name 86 // 1: abbreviated description 87 // 2: hostname 88 // 3: bech32 entity 89 // 4: Full content 90 91 let hostname = "https://damus.io"; 92 let abbrev_content = html_escape::encode_text(abbreviate(¬e_data.note.content, 64)); 93 let profile_name = html_escape::encode_text(¬e_data.profile.name); 94 95 write!( 96 data, 97 r#" 98 <html> 99 <head> 100 <title>{0} on nostr</title> 101 <meta name="viewport" content="width=device-width, initial-scale=1"> 102 <meta charset="UTF-8"> 103 104 <meta property="og:description" content="{1}" /> 105 <meta property="og:image" content="{2}/{3}.png"/> 106 <meta property="og:image:alt" content="{0}: {1}" /> 107 <meta property="og:image:height" content="600" /> 108 <meta property="og:image:width" content="1200" /> 109 <meta property="og:image:type" content="image/png" /> 110 <meta property="og:site_name" content="Damus" /> 111 <meta property="og:title" content="{0} on nostr" /> 112 <meta property="og:url" content="{2}/{3}"/> 113 <meta name="og:type" content="website"/> 114 <meta name="twitter:image:src" content="{2}/{3}.png" /> 115 <meta name="twitter:site" content="@damusapp" /> 116 <meta name="twitter:card" content="summary_large_image" /> 117 <meta name="twitter:title" content="{0} on nostr" /> 118 <meta name="twitter:description" content="{1}" /> 119 120 </head> 121 <body> 122 <h3>Note!</h3> 123 <div class="note"> 124 <div class="note-content">"#, 125 profile_name, 126 abbrev_content, 127 hostname, 128 nip19.to_bech32().unwrap() 129 )?; 130 131 let ok = (|| -> Result<(), nostrdb::Error> { 132 let txn = Transaction::new(&app.ndb)?; 133 let note_id = note_data.note.id.ok_or(nostrdb::Error::NotFound)?; 134 let note = app.ndb.get_note_by_id(&txn, ¬e_id)?; 135 let blocks = app.ndb.get_blocks_by_key(&txn, note.key().unwrap())?; 136 137 render_note_content(&mut data, &app.ndb, ¬e, &blocks); 138 139 Ok(()) 140 })(); 141 142 if let Err(err) = ok { 143 error!("error rendering html: {}", err); 144 write!( 145 data, 146 "{}", 147 html_escape::encode_text(¬e_data.note.content) 148 ); 149 } 150 151 write!( 152 data, 153 " 154 </div> 155 </div> 156 </body> 157 </html> 158 " 159 ); 160 161 Ok(Response::builder() 162 .header(header::CONTENT_TYPE, "text/html") 163 .status(StatusCode::OK) 164 .body(Full::new(Bytes::from(data)))?) 165 }