profile: move and type profile metadata

OFF0 2 years ago
parent cb0bd5ed2e
commit 2f5e08de04
Signed by: offbyn
GPG Key ID: 94A2F643C51F37FA

@ -1,15 +1,16 @@
import {nip19} from 'nostr-tools'; import {nip19} from 'nostr-tools';
import {zeroLeadingBitsCount} from './utils/crypto'; import {zeroLeadingBitsCount} from './utils/crypto';
import {elem, elemCanvas, parseTextContent} from './utils/dom'; import {elem, parseTextContent} from './utils/dom';
import {bounce, dateTime, formatTime} from './utils/time'; import {bounce, dateTime, formatTime} from './utils/time';
import {getHost, getNoxyUrl, isWssUrl} from './utils/url'; import {isWssUrl} from './utils/url';
import {sub24hFeed, subNote, subProfile} from './subscriptions' import {sub24hFeed, subNote, subProfile} from './subscriptions'
import {getReplyTo, hasEventTag, isMention, sortByCreatedAt, sortEventCreatedAt, validatePow} from './events'; import {getReplyTo, hasEventTag, isMention, sortByCreatedAt, sortEventCreatedAt, validatePow} from './events';
import {clearView, getViewContent, getViewElem, setViewElem, view} from './view'; import {clearView, getViewContent, getViewElem, setViewElem, view} from './view';
import {closeSettingsView, config, toggleSettingsView} from './settings'; import {closeSettingsView, config, toggleSettingsView} from './settings';
import {getReactions, getReactionContents, handleReaction, handleUpvote} from './reactions'; import {getReactions, getReactionContents, handleReaction, handleUpvote} from './reactions';
import {closePublishView, openWriteInput, togglePublishView} from './write'; import {closePublishView, openWriteInput, togglePublishView} from './write';
import {linkPreview, parseContent} from './media'; import {linkPreview} from './media';
import {getMetadata, handleMetadata} from './profiles';
// curl -H 'accept: application/nostr+json' https://relay.nostr.ch/ // curl -H 'accept: application/nostr+json' https://relay.nostr.ch/
@ -196,43 +197,6 @@ function handleRecommendServer(evt, relay) {
setViewElem(evt.id, art); setViewElem(evt.id, art);
} }
function handleContactList(evt, relay) {
if (getViewElem(evt.id)) {
return;
}
const art = renderUpdateContact(evt, relay);
if (textNoteList.length < 2) {
getViewContent().append(art);
return;
}
const closestTextNotes = textNoteList.sort(sortEventCreatedAt(evt.created_at));
getViewElem(closestTextNotes[0].id).after(art);
setViewElem(evt.id, art);
// const user = userList.find(u => u.pupkey === evt.pubkey);
// if (user) {
// console.log(`TODO: add contact list for ${evt.pubkey.slice(0, 8)} on ${relay}`, evt.tags);
// } else {
// tempContactList[relay] = tempContactList[relay]
// ? [...tempContactList[relay], evt]
// : [evt];
// }
}
function renderUpdateContact(evt, relay) {
const {img, time, userName} = getMetadata(evt, relay);
const body = elem('div', {className: 'mbox-body', title: dateTime.format(time)}, [
elem('header', {className: 'mbox-header'}, [
elem('small', {}, []),
]),
elem('pre', {title: JSON.stringify(evt.content)}, [
elem('strong', {}, userName),
' updated contacts: ',
JSON.stringify(evt.tags),
]),
]);
return renderArticle([img, body], {className: 'mbox-updated-contact', data: {id: evt.id, pubkey: evt.pubkey, relay}});
}
function renderRecommendServer(evt, relay) { function renderRecommendServer(evt, relay) {
const {img, name, time, userName} = getMetadata(evt, relay); const {img, name, time, userName} = getMetadata(evt, relay);
const body = elem('div', {className: 'mbox-body', title: dateTime.format(time)}, [ const body = elem('div', {className: 'mbox-body', title: dateTime.format(time)}, [
@ -253,76 +217,6 @@ function renderArticle(content, props = {}) {
return elem('article', {...props, className}, content); return elem('article', {...props, className}, content);
} }
const userList = [];
// const tempContactList = {};
function handleMetadata(evt, relay) {
const content = parseContent(evt.content);
if (content) {
setMetadata(evt, relay, content);
}
}
function setMetadata(evt, relay, content) {
let user = userList.find(u => u.pubkey === evt.pubkey);
const picture = getNoxyUrl('data', content.picture, evt.id, relay).href;
if (!user) {
user = {
metadata: {[relay]: content},
...(content.picture && {picture}),
pubkey: evt.pubkey,
};
userList.push(user);
} else {
user.metadata[relay] = {
...user.metadata[relay],
timestamp: evt.created_at,
...content,
};
// use only the first profile pic (for now), different pics on each releay are not supported yet
if (!user.picture) {
user.picture = picture;
}
}
// update profile images
if (user.picture && validatePow(evt)) {
document.body
.querySelectorAll(`canvas[data-pubkey="${evt.pubkey}"]`)
.forEach(canvas => (canvas.parentNode.replaceChild(elem('img', {src: user.picture}), canvas)));
}
if (user.metadata[relay].name) {
document.body
.querySelectorAll(`[data-id="${evt.pubkey}"] .mbox-username:not(.mbox-kind0-name)`)
.forEach(username => {
username.textContent = user.metadata[relay].name;
username.classList.add('mbox-kind0-name');
});
}
// if (tempContactList[relay]) {
// const updates = tempContactList[relay].filter(update => update.pubkey === evt.pubkey);
// if (updates) {
// console.log('TODO: add contact list (kind 3)', updates);
// }
// }
}
function getMetadata(evt, relay) {
const host = getHost(relay);
const user = userList.find(user => user.pubkey === evt.pubkey);
const userImg = user?.picture;
const name = user?.metadata[relay]?.name;
const userName = name || evt.pubkey.slice(0, 8);
const userAbout = user?.metadata[relay]?.about || '';
const img = (userImg && validatePow(evt)) ? elem('img', {
alt: `${userName} ${host}`,
loading: 'lazy',
src: userImg,
title: `${userName} on ${host} ${userAbout}`,
}) : elemCanvas(evt.pubkey);
const time = new Date(evt.created_at * 1000);
return {host, img, name, time, userName};
}
// subscribe and change view // subscribe and change view
function route(path) { function route(path) {
if (path === '/') { if (path === '/') {

@ -1,7 +1,7 @@
import { elem } from './utils/dom'; import { elem } from './utils/dom';
import { getNoxyUrl } from './utils/url'; import { getNoxyUrl } from './utils/url';
export const parseContent = (content: string) => { export const parseContent = (content: string): unknown => {
try { try {
return JSON.parse(content); return JSON.parse(content);
} catch(err) { } catch(err) {

@ -0,0 +1,158 @@
import {Event} from 'nostr-tools';
import {elem, elemCanvas} from './utils/dom';
import {getHost, getNoxyUrl} from './utils/url';
import {validatePow} from './events';
import {parseContent} from './media';
type Metadata = {
name?: string;
about?: string;
picture?: string;
};
type Profile = {
metadata: {
[relay: string]: Metadata;
};
name?: string;
picture?: string;
pubkey: string;
};
const userList: Array<Profile> = [];
// const tempContactList = {};
const setMetadata = (
evt: Event,
relay: string,
metadata: Metadata,
) => {
let user = userList.find(u => u.pubkey === evt.pubkey);
if (!user) {
user = {
metadata: {[relay]: metadata},
pubkey: evt.pubkey,
};
userList.push(user);
} else {
user.metadata[relay] = {
...user.metadata[relay],
// timestamp: evt.created_at,
...metadata,
};
}
// store the first seen name (for now) as main user.name
if (!user.name && metadata.name) {
user.name = metadata.name;
}
// use the first seen profile pic (for now), pics from different relays are not supported yet
if (!user.picture && metadata.picture) {
const imgUrl = getNoxyUrl('data', metadata.picture, evt.id, relay);
if (imgUrl) {
user.picture = imgUrl.href;
// 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));
}
}
}
// update profile names
const name = user.metadata[relay].name || user.name || '';
if (name) {
document.body
.querySelectorAll(`[data-pubkey="${evt.pubkey}"] .mbox-username:not(.mbox-kind0-name)`)
.forEach((username: HTMLElement) => {
username.textContent = name;
username.classList.add('mbox-kind0-name');
});
}
// if (tempContactList[relay]) {
// const updates = tempContactList[relay].filter(update => update.pubkey === evt.pubkey);
// if (updates) {
// console.log('TODO: add contact list (kind 3)', updates);
// }
// }
};
export const handleMetadata = (evt: Event, relay: string) => {
const content = parseContent(evt.content);
if (!content || typeof content !== 'object' || Array.isArray(content)) {
console.warn('expected nip-01 JSON object with user info, but got something funny', evt);
return;
}
const hasNameString = 'name' in content && typeof content.name === 'string';
const hasAboutString = 'about' in content && typeof content.about === 'string';
const hasPictureString = 'picture' in content && typeof content.picture === 'string';
// custom
const hasDisplayName = 'display_name' in content && typeof content.display_name === 'string';
if (!hasNameString && !hasAboutString && !hasPictureString && !hasDisplayName) {
console.warn('expected basic nip-01 user info (name, about, picture) but nothing found', evt);
return;
}
const metadata: Metadata = {
...(hasNameString && {name: content.name as string} || hasDisplayName && {name: content.display_name as string}),
...(hasAboutString && {about: content.about as string}),
...(hasPictureString && {picture: content.picture as string}),
};
setMetadata(evt, relay, metadata);
};
export const getMetadata = (evt: Event, relay: string) => {
const host = getHost(relay);
const user = userList.find(user => user.pubkey === evt.pubkey);
const userImg = user?.picture;
const name = user?.metadata[relay]?.name || user?.name;
const userName = name || evt.pubkey.slice(0, 8);
const userAbout = user?.metadata[relay]?.about || '';
const img = (userImg && validatePow(evt)) ? elem('img', {
alt: `${userName} ${host}`,
loading: 'lazy',
src: userImg,
title: `${userName} on ${host} ${userAbout}`,
}) : elemCanvas(evt.pubkey);
const time = new Date(evt.created_at * 1000);
return {host, img, name, time, userName};
};
/* export function handleContactList(evt, relay) {
if (getViewElem(evt.id)) {
return;
}
const art = renderUpdateContact(evt, relay);
if (textNoteList.length < 2) {
getViewContent().append(art);
return;
}
const closestTextNotes = textNoteList.sort(sortEventCreatedAt(evt.created_at));
getViewElem(closestTextNotes[0].id).after(art);
setViewElem(evt.id, art);
// const user = userList.find(u => u.pupkey === evt.pubkey);
// if (user) {
// console.log(`TODO: add contact list for ${evt.pubkey.slice(0, 8)} on ${relay}`, evt.tags);
// } else {
// tempContactList[relay] = tempContactList[relay]
// ? [...tempContactList[relay], evt]
// : [evt];
// }
} */
// function renderUpdateContact(evt, relay) {
// const {img, time, userName} = getMetadata(evt, relay);
// const body = elem('div', {className: 'mbox-body', title: dateTime.format(time)}, [
// elem('header', {className: 'mbox-header'}, [
// elem('small', {}, []),
// ]),
// elem('pre', {title: JSON.stringify(evt.content)}, [
// elem('strong', {}, userName),
// ' updated contacts: ',
// JSON.stringify(evt.tags),
// ]),
// ]);
// return renderArticle([img, body], {className: 'mbox-updated-contact', data: {id: evt.id, pubkey: evt.pubkey, relay}});
// }
Loading…
Cancel
Save