Generate a deterministic name from pubkey if no display name is set

This commit is contained in:
Alex Gleason 2025-06-01 09:28:20 -05:00
parent 865ca7039f
commit 7c5fe75572
No known key found for this signature in database
GPG Key ID: 7211D1F99744FBB7
2 changed files with 71 additions and 3 deletions

View File

@ -101,4 +101,36 @@ describe('NoteContent', () => {
expect(nostrHashtag).toHaveAttribute('href', '/t/nostr'); expect(nostrHashtag).toHaveAttribute('href', '/t/nostr');
expect(bitcoinHashtag).toHaveAttribute('href', '/t/bitcoin'); expect(bitcoinHashtag).toHaveAttribute('href', '/t/bitcoin');
}); });
it('generates deterministic names for users without metadata and styles them differently', () => {
// Use a valid npub for testing
const event: NostrEvent = {
id: 'test-id',
pubkey: 'test-pubkey',
created_at: Math.floor(Date.now() / 1000),
kind: 1,
tags: [],
content: `Mentioning nostr:npub1zg69v7ys40x77y352eufp27daufrg4ncjz4ummcjx3t83y9tehhsqepuh0`,
sig: 'test-sig',
};
render(
<TestApp>
<NoteContent event={event} />
</TestApp>
);
// The mention should be rendered with a deterministic name
const mention = screen.getByRole('link');
expect(mention).toBeInTheDocument();
// Should have muted styling for generated names (gray instead of blue)
expect(mention).toHaveClass('text-gray-500');
expect(mention).not.toHaveClass('text-blue-500');
// The text should start with @ and contain a generated name (not a truncated npub)
const linkText = mention.textContent;
expect(linkText).not.toMatch(/^@npub1/); // Should not be a truncated npub
expect(linkText).toEqual("@SwiftFalcon");
});
}); });

View File

@ -20,7 +20,7 @@ export function NoteContent({
const text = event.content; const text = event.content;
// Regex to find URLs, Nostr references, and hashtags // Regex to find URLs, Nostr references, and hashtags
const regex = /(https?:\/\/[^\s]+)|nostr:(npub1|note1|nprofile1|nevent1)([a-z0-9]+)|(#\w+)/g; const regex = /(https?:\/\/[^\s]+)|nostr:(npub1|note1|nprofile1|nevent1)([023456789acdefghjklmnpqrstuvwxyz]+)|(#\w+)/g;
const parts: React.ReactNode[] = []; const parts: React.ReactNode[] = [];
let lastIndex = 0; let lastIndex = 0;
@ -117,14 +117,50 @@ export function NoteContent({
function NostrMention({ pubkey }: { pubkey: string }) { function NostrMention({ pubkey }: { pubkey: string }) {
const author = useAuthor(pubkey); const author = useAuthor(pubkey);
const npub = nip19.npubEncode(pubkey); const npub = nip19.npubEncode(pubkey);
const displayName = author.data?.metadata?.name ?? npub.slice(0, 8); const hasRealName = !!author.data?.metadata?.name;
const displayName = author.data?.metadata?.name ?? generateDeterministicName(pubkey);
return ( return (
<Link <Link
to={`/${npub}`} to={`/${npub}`}
className="font-medium text-blue-500 hover:underline" className={cn(
"font-medium hover:underline",
hasRealName
? "text-blue-500"
: "text-gray-500 hover:text-gray-700"
)}
> >
@{displayName} @{displayName}
</Link> </Link>
); );
}
// Generate a deterministic name based on pubkey
function generateDeterministicName(pubkey: string): string {
// Use a simple hash of the pubkey to generate consistent adjective + noun combinations
const adjectives = [
'Swift', 'Bright', 'Calm', 'Bold', 'Wise', 'Kind', 'Quick', 'Brave',
'Cool', 'Sharp', 'Clear', 'Strong', 'Smart', 'Fast', 'Keen', 'Pure',
'Noble', 'Gentle', 'Fierce', 'Steady', 'Clever', 'Proud', 'Silent', 'Wild'
];
const nouns = [
'Fox', 'Eagle', 'Wolf', 'Bear', 'Lion', 'Tiger', 'Hawk', 'Owl',
'Deer', 'Raven', 'Falcon', 'Lynx', 'Otter', 'Whale', 'Shark', 'Dolphin',
'Phoenix', 'Dragon', 'Panther', 'Jaguar', 'Cheetah', 'Leopard', 'Puma', 'Cobra'
];
// Create a simple hash from the pubkey
let hash = 0;
for (let i = 0; i < pubkey.length; i++) {
const char = pubkey.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash; // Convert to 32-bit integer
}
// Use absolute value to ensure positive index
const adjIndex = Math.abs(hash) % adjectives.length;
const nounIndex = Math.abs(hash >> 8) % nouns.length;
return `${adjectives[adjIndex]}${nouns[nounIndex]}`;
} }