Files
smesh/src/lib/pubkey.ts
2025-11-02 16:59:05 +08:00

114 lines
3.1 KiB
TypeScript

import { LRUCache } from 'lru-cache'
import { nip19 } from 'nostr-tools'
export function formatPubkey(pubkey: string) {
const npub = pubkeyToNpub(pubkey)
if (npub) {
return formatNpub(npub)
}
return pubkey.slice(0, 4) + '...' + pubkey.slice(-4)
}
export function formatNpub(npub: string, length = 12) {
if (length < 12) {
length = 12
}
if (length >= 63) {
return npub
}
const prefixLength = Math.floor((length - 5) / 2) + 5
const suffixLength = length - prefixLength
return npub.slice(0, prefixLength) + '...' + npub.slice(-suffixLength)
}
export function formatUserId(userId: string) {
if (userId.startsWith('npub1')) {
return formatNpub(userId)
}
return formatPubkey(userId)
}
export function pubkeyToNpub(pubkey: string) {
try {
return nip19.npubEncode(pubkey)
} catch {
return null
}
}
export function userIdToPubkey(userId: string, throwOnInvalid = false): string {
if (userId.startsWith('npub1') || userId.startsWith('nprofile1')) {
try {
const { type, data } = nip19.decode(userId)
if (type === 'npub') {
return data
} else if (type === 'nprofile') {
return data.pubkey
}
} catch (error) {
if (throwOnInvalid) {
throw new Error('Invalid id')
}
console.error('Error decoding userId:', userId, 'error:', error)
}
}
return userId
}
export function isValidPubkey(pubkey: string) {
return /^[0-9a-f]{64}$/.test(pubkey)
}
const pubkeyImageCache = new LRUCache<string, string>({ max: 1000 })
export function generateImageByPubkey(pubkey: string): string {
if (pubkeyImageCache.has(pubkey)) {
return pubkeyImageCache.get(pubkey)!
}
const paddedPubkey = pubkey.padEnd(2, '0')
// Split into 3 parts for colors and the rest for control points
const colors: string[] = []
const controlPoints: string[] = []
for (let i = 0; i < 11; i++) {
const part = paddedPubkey.slice(i * 6, (i + 1) * 6)
if (i < 3) {
colors.push(`#${part}`)
} else {
controlPoints.push(part)
}
}
// Generate SVG with multiple radial gradients
const gradients = controlPoints
.map((point, index) => {
const cx = parseInt(point.slice(0, 2), 16) % 100
const cy = parseInt(point.slice(2, 4), 16) % 100
const r = (parseInt(point.slice(4, 6), 16) % 35) + 30
const c = colors[index % (colors.length - 1)]
return `
<radialGradient id="grad${index}-${pubkey}" cx="${cx}%" cy="${cy}%" r="${r}%">
<stop offset="0%" style="stop-color:${c};stop-opacity:1" />
<stop offset="100%" style="stop-color:${c};stop-opacity:0" />
</radialGradient>
<rect width="100%" height="100%" fill="url(#grad${index}-${pubkey})" />
`
})
.join('')
const image = `
<svg width="100" height="100" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<rect width="100%" height="100%" fill="${colors[2]}" fill-opacity="0.3" />
${gradients}
</svg>
`
const imageData = `data:image/svg+xml;base64,${btoa(image)}`
pubkeyImageCache.set(pubkey, imageData)
return imageData
}