|
|
@ -3,9 +3,9 @@ import {elem} from './domutil.js';
|
|
|
|
// curl -H 'accept: application/nostr+json' https://nostr.x1ddos.ch
|
|
|
|
// curl -H 'accept: application/nostr+json' https://nostr.x1ddos.ch
|
|
|
|
const pool = relayPool();
|
|
|
|
const pool = relayPool();
|
|
|
|
pool.addRelay('wss://nostr.x1ddos.ch', {read: true, write: true});
|
|
|
|
pool.addRelay('wss://nostr.x1ddos.ch', {read: true, write: true});
|
|
|
|
pool.addRelay('wss://nostr.bitcoiner.social/', {read: true, write: true});
|
|
|
|
// pool.addRelay('wss://nostr.bitcoiner.social/', {read: true, write: true});
|
|
|
|
// pool.addRelay('wss://nostr.openchain.fr', {read: true, write: true});
|
|
|
|
pool.addRelay('wss://nostr.openchain.fr', {read: true, write: true});
|
|
|
|
// pool.addRelay('wss://relay.nostr.info', {read: true, write: true});
|
|
|
|
pool.addRelay('wss://relay.nostr.info', {read: true, write: true});
|
|
|
|
// pool.addRelay('wss://relay.damus.io', {read: true, write: true});
|
|
|
|
// pool.addRelay('wss://relay.damus.io', {read: true, write: true});
|
|
|
|
// read only
|
|
|
|
// read only
|
|
|
|
// pool.addRelay('wss://nostr.rocks', {read: true, write: false});
|
|
|
|
// pool.addRelay('wss://nostr.rocks', {read: true, write: false});
|
|
|
@ -18,7 +18,7 @@ const dateTime = new Intl.DateTimeFormat('de-ch' /* navigator.language */, {
|
|
|
|
let max = 0;
|
|
|
|
let max = 0;
|
|
|
|
|
|
|
|
|
|
|
|
function onEvent(evt, relay) {
|
|
|
|
function onEvent(evt, relay) {
|
|
|
|
if (max++ >= 2123) {
|
|
|
|
if (max++ >= 223) {
|
|
|
|
return subscription.unsub();
|
|
|
|
return subscription.unsub();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
switch (evt.kind) {
|
|
|
|
switch (evt.kind) {
|
|
|
@ -35,7 +35,7 @@ function onEvent(evt, relay) {
|
|
|
|
updateContactList(evt, relay);
|
|
|
|
updateContactList(evt, relay);
|
|
|
|
break;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
default:
|
|
|
|
console.log(`TODO: add support for event kind ${evt.kind}`, evt)
|
|
|
|
// console.log(`TODO: add support for event kind ${evt.kind}`/*, evt*/)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -52,7 +52,7 @@ const subscription = pool.sub({
|
|
|
|
// // pubkey, // me
|
|
|
|
// // pubkey, // me
|
|
|
|
// '32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245', // jb55
|
|
|
|
// '32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245', // jb55
|
|
|
|
// ],
|
|
|
|
// ],
|
|
|
|
limit: 2000,
|
|
|
|
limit: 200,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
@ -66,7 +66,14 @@ function handleTextNote(evt, relay) {
|
|
|
|
eventRelayMap[evt.id] = [relay, ...(eventRelayMap[evt.id])];
|
|
|
|
eventRelayMap[evt.id] = [relay, ...(eventRelayMap[evt.id])];
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
eventRelayMap[evt.id] = [relay];
|
|
|
|
eventRelayMap[evt.id] = [relay];
|
|
|
|
(evt.tags.some(hasEventTag) ? replyList : textNoteList).push(evt);
|
|
|
|
if (evt.tags.some(hasEventTag)) {
|
|
|
|
|
|
|
|
replyList.push(evt)
|
|
|
|
|
|
|
|
if (feedDomMap[evt.tags[0][1]]) {
|
|
|
|
|
|
|
|
console.log('CALL ME', evt.tags[0][1], feedDomMap[evt.tags[0][1]]);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
textNoteList.push(evt);
|
|
|
|
|
|
|
|
}
|
|
|
|
renderFeed();
|
|
|
|
renderFeed();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -81,45 +88,57 @@ const sortByCreatedAt = (evt1, evt2) => {
|
|
|
|
return evt1.created_at > evt2.created_at ? -1 : 1;
|
|
|
|
return evt1.created_at > evt2.created_at ? -1 : 1;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// let debounceDebugMessageTimer;
|
|
|
|
let debounceDebugMessageTimer;
|
|
|
|
function renderFeed() {
|
|
|
|
function renderFeed() {
|
|
|
|
const sortedFeeds = textNoteList.sort(sortByCreatedAt).reverse();
|
|
|
|
const sortedFeeds = textNoteList.sort(sortByCreatedAt).reverse();
|
|
|
|
// clearTimeout(debounceDebugMessageTimer);
|
|
|
|
// debug
|
|
|
|
// debounceDebugMessageTimer = setTimeout(() => {
|
|
|
|
clearTimeout(debounceDebugMessageTimer);
|
|
|
|
// console.log(`${sortedFeeds.map(e => dateTime.format(e.created_at * 1000)).join('\n')}`)
|
|
|
|
debounceDebugMessageTimer = setTimeout(() => {
|
|
|
|
// }, 2000);
|
|
|
|
console.log(`${sortedFeeds.reverse().map(e => dateTime.format(e.created_at * 1000)).join('\n')}`)
|
|
|
|
|
|
|
|
}, 2000);
|
|
|
|
sortedFeeds.forEach((textNoteEvent, i) => {
|
|
|
|
sortedFeeds.forEach((textNoteEvent, i) => {
|
|
|
|
if (feedDomMap[textNoteEvent.id]) {
|
|
|
|
if (feedDomMap[textNoteEvent.id]) {
|
|
|
|
// TODO check eventRelayMap if event was published to different relays
|
|
|
|
// TODO check eventRelayMap if event was published to different relays
|
|
|
|
return;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const art = renderTextNote(textNoteEvent, eventRelayMap[textNoteEvent.id]);
|
|
|
|
const article = createTextNote(textNoteEvent, eventRelayMap[textNoteEvent.id]);
|
|
|
|
feedDomMap[textNoteEvent.id] = art;
|
|
|
|
feedDomMap[textNoteEvent.id] = article;
|
|
|
|
if (i === 0) {
|
|
|
|
if (i === 0) {
|
|
|
|
feedContainer.append(art);
|
|
|
|
feedContainer.append(article);
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
feedDomMap[sortedFeeds[i - 1].id].before(art);
|
|
|
|
feedDomMap[sortedFeeds[i - 1].id].before(article);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const getShortTagId = tag => `${tag[1].slice(0, 7)}${tag[2] ? '@' + tag[2] : ''}`;
|
|
|
|
const sortEventCreatedAt = (evt) => (
|
|
|
|
const sortCreatedAt = ({created_at: a}, {created_at: b}) => (
|
|
|
|
{created_at: a},
|
|
|
|
|
|
|
|
{created_at: b},
|
|
|
|
|
|
|
|
) => (
|
|
|
|
Math.abs(a - evt.created_at) < Math.abs(b - evt.created_at) ? -1 : 1
|
|
|
|
Math.abs(a - evt.created_at) < Math.abs(b - evt.created_at) ? -1 : 1
|
|
|
|
);
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
function renderTextNote(evt, relay) {
|
|
|
|
function createTextNote(evt, relay) {
|
|
|
|
const [host, img, time, userName] = getMetadata(evt, relay);
|
|
|
|
const {host, img, isReply, replies, time, userName} = getMetadata(evt, relay);
|
|
|
|
const isReply = evt.tags.some(hasEventTag);
|
|
|
|
const headerInfo = isReply ? [
|
|
|
|
|
|
|
|
elem('strong', {title: evt.pubkey}, userName)
|
|
|
|
|
|
|
|
] : [
|
|
|
|
|
|
|
|
elem('strong', {title: evt.pubkey}, userName),
|
|
|
|
|
|
|
|
elem('span', {
|
|
|
|
|
|
|
|
title: `Event ${evt.id}
|
|
|
|
|
|
|
|
${isReply ? `\nReply ${evt.tags[0][1]}\n` : ''}\n${time}`
|
|
|
|
|
|
|
|
}, ` on ${host} `),
|
|
|
|
|
|
|
|
];
|
|
|
|
const body = elem('div', {className: 'mbox-body'}, [
|
|
|
|
const body = elem('div', {className: 'mbox-body'}, [
|
|
|
|
renderProfile(userName, host),
|
|
|
|
elem('header', {
|
|
|
|
elem('div', {}, [
|
|
|
|
className: 'mbox-header',
|
|
|
|
elem('small', {}, isReply ? `reply to ${evt.tags.filter(hasEventTag).map(getShortTagId).join(' and ')}` : evt.id),
|
|
|
|
}, [
|
|
|
|
elem('time', {dateTime: time, title: time}, ` on ${dateTime.format(time)}`),
|
|
|
|
elem('small', {}, headerInfo),
|
|
|
|
]),
|
|
|
|
]),
|
|
|
|
evt.content, // text
|
|
|
|
evt.content, // text
|
|
|
|
|
|
|
|
replies[0] ? elem('div', {className: 'mobx-replies'}, replies.map(e => createTextNote(e, relay))) : '',
|
|
|
|
]);
|
|
|
|
]);
|
|
|
|
return rendernArticle([img, body], isReply && {className: 'mbox-reply'});
|
|
|
|
return rendernArticle([img, body]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function handleRecommendServer(evt, relay) {
|
|
|
|
function handleRecommendServer(evt, relay) {
|
|
|
@ -132,26 +151,25 @@ function handleRecommendServer(evt, relay) {
|
|
|
|
feedContainer.append(art);
|
|
|
|
feedContainer.append(art);
|
|
|
|
return;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const closestTextNotes = textNoteList.sort(sortCreatedAt);
|
|
|
|
const closestTextNotes = textNoteList.sort(sortEventCreatedAt(evt));
|
|
|
|
feedDomMap[closestTextNotes[0].id].after(art);
|
|
|
|
feedDomMap[closestTextNotes[0].id].after(art);
|
|
|
|
feedDomMap[evt.id] = art;
|
|
|
|
feedDomMap[evt.id] = art;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function renderRecommendServer(evt, relay) {
|
|
|
|
function renderRecommendServer(evt, relay) {
|
|
|
|
const [host, img, time, userName] = getMetadata(evt, relay);
|
|
|
|
const {host, img, 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)}, [
|
|
|
|
renderProfile(userName, host),
|
|
|
|
elem('header', {className: 'mbox-header'}, [
|
|
|
|
|
|
|
|
elem('small', {}, [
|
|
|
|
|
|
|
|
elem('strong', {}, userName),
|
|
|
|
|
|
|
|
` on ${host} `,
|
|
|
|
|
|
|
|
]),
|
|
|
|
|
|
|
|
]),
|
|
|
|
`recommends server: ${evt.content}`,
|
|
|
|
`recommends server: ${evt.content}`,
|
|
|
|
]);
|
|
|
|
]);
|
|
|
|
return rendernArticle([img, body], {className: 'mbox-recommend-server'});
|
|
|
|
return rendernArticle([img, body], {className: 'mbox-recommend-server'});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function renderProfile(userName, host) {
|
|
|
|
|
|
|
|
return elem('header', {className: 'mbox-header'}, [
|
|
|
|
|
|
|
|
elem('small', {}, [elem('strong', {}, userName), ` on ${host} `]),
|
|
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function rendernArticle(content, props) {
|
|
|
|
function rendernArticle(content, props) {
|
|
|
|
const className = ['mbox', props?.className].join(' ');
|
|
|
|
const className = ['mbox', props?.className].join(' ');
|
|
|
|
return elem('article', {...props, className}, content);
|
|
|
|
return elem('article', {...props, className}, content);
|
|
|
@ -189,25 +207,36 @@ function setMetadata(evt, relay, content) {
|
|
|
|
if (tempContactList[relay]) {
|
|
|
|
if (tempContactList[relay]) {
|
|
|
|
const updates = tempContactList[relay].filter(update => update.pubkey === evt.pubkey);
|
|
|
|
const updates = tempContactList[relay].filter(update => update.pubkey === evt.pubkey);
|
|
|
|
if (updates) {
|
|
|
|
if (updates) {
|
|
|
|
console.log('TODO: add contact list (kind 3)', updates);
|
|
|
|
// console.log('TODO: add contact list (kind 3)', updates);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const getHost = (url) => {
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
return new URL(url).host;
|
|
|
|
|
|
|
|
} catch(e) {
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function getMetadata(evt, relay) {
|
|
|
|
function getMetadata(evt, relay) {
|
|
|
|
const {host} = new URL(relay);
|
|
|
|
const host = getHost(relay[0]);
|
|
|
|
const user = userList.find(user => user.pubkey === evt.pubkey);
|
|
|
|
const user = userList.find(user => user.pubkey === evt.pubkey);
|
|
|
|
const userImg = /*user?.metadata[relay]?.picture || */'bubble.svg'; // TODO: enable pic once we have proxy
|
|
|
|
const userImg = /*user?.metadata[relay]?.picture || */'bubble.svg'; // TODO: enable pic once we have proxy
|
|
|
|
const userName = user?.metadata[relay]?.name || evt.pubkey.slice(0, 8);
|
|
|
|
const userName = user?.metadata[relay]?.name || evt.pubkey.slice(0, 8);
|
|
|
|
const userAbout = user?.metadata[relay]?.about || '';
|
|
|
|
const userAbout = user?.metadata[relay]?.about || '';
|
|
|
|
|
|
|
|
const isReply = evt.tags.some(hasEventTag);
|
|
|
|
const img = elem('img', {
|
|
|
|
const img = elem('img', {
|
|
|
|
className: 'mbox-img',
|
|
|
|
className: 'mbox-img',
|
|
|
|
src: userImg,
|
|
|
|
src: userImg,
|
|
|
|
alt: `${userName}@${host}`,
|
|
|
|
alt: `${userName}@${host}`,
|
|
|
|
title: userAbout},
|
|
|
|
title: userAbout},
|
|
|
|
'');
|
|
|
|
''
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
const replies = replyList.filter((reply) => reply.tags[0][1] === evt.id);
|
|
|
|
const time = new Date(evt.created_at * 1000);
|
|
|
|
const time = new Date(evt.created_at * 1000);
|
|
|
|
return [host, img, time, userName];
|
|
|
|
return {host, img, isReply, replies, time, userName};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function updateContactList(evt, relay) {
|
|
|
|
function updateContactList(evt, relay) {
|
|
|
|