Skip to content

Commit 9b6b260

Browse files
Add nevent links on timestamps and NIP-05 verification
Timestamps link to nostr.at/nevent1... with relay hints and author. NIP-05 defaults to _@domain and shows verification status.
1 parent b728e5f commit 9b6b260

1 file changed

Lines changed: 37 additions & 1 deletion

File tree

index.html

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,13 +292,49 @@ <h1>Nostr</h1>
292292
return new Date(ts * 1000).toLocaleDateString()
293293
}
294294

295+
const BECH32_CHARS = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l'
296+
function bech32Encode(hrp, data5bit) {
297+
function polymod(values) {
298+
const GEN = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]
299+
let chk = 1
300+
for (const v of values) { const b = chk >> 25; chk = ((chk & 0x1ffffff) << 5) ^ v; for (let i = 0; i < 5; i++) if ((b >> i) & 1) chk ^= GEN[i] }
301+
return chk
302+
}
303+
function hrpExpand(h) { const r = []; for (let i = 0; i < h.length; i++) r.push(h.charCodeAt(i) >> 5); r.push(0); for (let i = 0; i < h.length; i++) r.push(h.charCodeAt(i) & 31); return r }
304+
const values = hrpExpand(hrp).concat(data5bit)
305+
const pm = polymod(values.concat([0,0,0,0,0,0])) ^ 1
306+
const checksum = []
307+
for (let i = 0; i < 6; i++) checksum.push((pm >> (5 * (5 - i))) & 31)
308+
return hrp + '1' + data5bit.concat(checksum).map(d => BECH32_CHARS[d]).join('')
309+
}
310+
function convertBits(data, fromBits, toBits, pad) {
311+
let acc = 0, bits = 0; const ret = [], maxv = (1 << toBits) - 1
312+
for (const v of data) { acc = (acc << fromBits) | v; bits += fromBits; while (bits >= toBits) { bits -= toBits; ret.push((acc >> bits) & maxv) } }
313+
if (pad) { if (bits > 0) ret.push((acc << (toBits - bits)) & maxv) }
314+
return ret
315+
}
316+
function hexToU8(hex) { const b = []; for (let i = 0; i < hex.length; i += 2) b.push(parseInt(hex.slice(i, i + 2), 16)); return new Uint8Array(b) }
317+
function neventEncode(eventId, relays, author) {
318+
const tlv = []
319+
const idBytes = hexToU8(eventId)
320+
tlv.push(0, 32, ...idBytes)
321+
if (relays) for (const r of relays) { const rb = new TextEncoder().encode(r); tlv.push(1, rb.length, ...rb) }
322+
if (author) { const ab = hexToU8(author); tlv.push(2, 32, ...ab) }
323+
return bech32Encode('nevent', convertBits(tlv, 8, 5, true))
324+
}
325+
295326
function buildNoteHtml(evt) {
296327
const short = evt.pubkey.slice(0, 8) + '...' + evt.pubkey.slice(-4)
297328
const isMe = pubkeyHex && evt.pubkey === pubkeyHex
329+
const noteId = evt.id ? neventEncode(evt.id, RELAYS, evt.pubkey) : ''
330+
const timeStr = timeAgo(evt.created_at)
331+
const timeHtml = noteId
332+
? '<a href="https://nostr.at/' + noteId + '" target="_blank" rel="noopener" style="color:inherit;text-decoration:none" title="' + noteId + '">' + timeStr + '</a>'
333+
: timeStr
298334
return '<div class="note" data-ts="' + evt.created_at + '">' +
299335
'<div class="note-header">' +
300336
'<span class="note-pubkey">' + (isMe ? 'you' : short) + '</span>' +
301-
'<span>' + timeAgo(evt.created_at) + '</span>' +
337+
'<span>' + timeHtml + '</span>' +
302338
'</div>' +
303339
'<div class="note-content">' + formatContent(evt.content) + '</div>' +
304340
'</div>'

0 commit comments

Comments
 (0)