import {Event} from 'nostr-tools'; import {elem, elemCanvas, parseTextContent} from './utils/dom'; import {getNoxyUrl} from './utils/url'; import {getViewElem, getViewOptions} from './view'; import {parseJSON} from './media'; import {sortByCreatedAt} from './events'; type Profile = { name: string; about?: string; picture?: string; website?: string; } const transformMetadata = (data: unknown): Profile | undefined => { if (!data || typeof data !== 'object' || Array.isArray(data)) { console.warn('expected nip-01 JSON object with user info, but got something funny', data); return; } const hasNameString = 'name' in data && typeof data.name === 'string'; const hasAboutString = 'about' in data && typeof data.about === 'string'; const hasPictureString = 'picture' in data && typeof data.picture === 'string'; // custom const hasDisplayName = 'display_name' in data && typeof data.display_name === 'string'; const hasWebsite = 'website' in data && typeof data.website === 'string'; if (!hasNameString && !hasAboutString && !hasPictureString && !hasDisplayName) { console.warn('expected basic nip-01 user info (name, about, picture) but nothing found', data); return; } const name = ( hasNameString && data.name as string || hasDisplayName && data.display_name as string || '' ); return { name, ...(hasAboutString && {about: data.about as string}), ...(hasPictureString && {picture: data.picture as string}), ...(hasWebsite && {website: data.website as string}) }; }; const profileMap: { [pubkey: string]: Profile } = {}; const metadataList: Array = []; export const handleMetadata = (evt: Event, relay: string) => { if (metadataList.find(({id}) => id === evt.id)) { return; } const contactEventList = metadataList.filter(({pubkey}) => pubkey === evt.pubkey); metadataList.push(evt); contactEventList.push(evt); contactEventList.sort(sortByCreatedAt); if (contactEventList.some(({created_at}) => created_at > evt.created_at) ) { // evt is older return; } const content = parseJSON(evt.content); const metadata = transformMetadata(content); if (!metadata) { return; } profileMap[evt.pubkey] = metadata; if (metadata.picture) { const imgUrl = getNoxyUrl('data', metadata.picture, evt.id, relay); if (imgUrl) { // update profile images that used some nip-13 work // if (imgUrl.href && validatePow(evt)) { // document.body // .querySelectorAll(`canvas[data-pubkey="${evt.pubkey}"]`) // .forEach(canvas => canvas.parentNode?.replaceChild(elem('img', {src: imgUrl.href}), canvas)); // } } } if (metadata.name) { // update profile names document.body .querySelectorAll(`[data-profile="${evt.pubkey}"]`) .forEach((username: HTMLElement) => { username.textContent = metadata.name; username.classList.add('mbox-kind0-name'); }); } if (metadata.about) { const about = getViewElem(`about-${evt.pubkey}`); if (about) { const view = getViewOptions(); about.replaceChildren(...parseTextContent( view.type === 'contacts' ? metadata.about.split('\n')[0] : metadata.about )[0]); } } }; export const getMetadata = (pubkey: string) => { const user = profileMap[pubkey]; const about = user?.about; const name = user?.name; const userName = name || pubkey.slice(0, 8); // const userImg = user?.picture; const img = /* (userImg && validatePow(evt)) ? elem('img', { alt: `${userName} ${host}`, loading: 'lazy', src: userImg, title: `${userName} on ${host} ${userAbout}`, }) : */ elemCanvas(pubkey); return {about, img, name, userName}; }; export const renderProfile = (pubkey: string) => { const header = getViewElem('header'); const metadata = profileMap[pubkey]; if (!header || !metadata) { return; } if (metadata.name) { const h1 = header.querySelector('h1'); if (h1) { h1.textContent = metadata.name; document.title = metadata.name; } else { header.append(elem('h1', {}, metadata.name)); } } const detail = getViewElem(`detail-${pubkey}`); if (metadata.about && !detail.children.length) { const [content] = parseTextContent(metadata.about); detail?.append(...content); } if (metadata.website) { const website = detail.querySelector('[data-website]'); if (website) { const url = metadata.website.toLowerCase().startsWith('http') ? metadata.website : `https://${metadata.website}`; const [content] = parseTextContent(url); website.replaceChildren(...content); (website as HTMLDivElement).hidden = false; } else { detail.append(elem('div', {data: {website: ''}}, metadata.name)); } } };